mirror of https://github.com/boxyhq/jackson.git
Use a global certificate instead of a per tenant/product certificate (#667)
* Replace Admin UI with Admin Portal * Create a default certificate * Use the default certs instead of per connection certificate * Revert the changes * refactored to encapsulate all logic inside x509.ts * added certs to sp-metadata * Cache the certificate before return * Fix the type * added expiry check to cached certificate * added url to download public cert * added instructions to encrypt assertion * bumped up version Co-authored-by: Deepak Prabhakara <deepak@boxyhq.com>
This commit is contained in:
parent
1674fd5afa
commit
6adb642266
|
@ -14,6 +14,11 @@ const links = [
|
|||
'The configuration setup guide that your customers will need to refer to when setting up SAML application with their Identity Provider.',
|
||||
href: '/.well-known/saml-configuration',
|
||||
},
|
||||
{
|
||||
title: 'SAML Public Certificate',
|
||||
description: 'The SAML Public Certificate if you want to enable encryption with your Identity Provider.',
|
||||
href: '/.well-known/saml.cer',
|
||||
},
|
||||
{
|
||||
title: 'OpenID Configuration',
|
||||
description:
|
||||
|
|
|
@ -29,6 +29,10 @@ module.exports = {
|
|||
},
|
||||
rewrites: async () => {
|
||||
return [
|
||||
{
|
||||
source: '/.well-known/saml.cer',
|
||||
destination: '/api/well-known/saml.cer',
|
||||
},
|
||||
{
|
||||
source: '/.well-known/openid-configuration',
|
||||
destination: '/api/well-known/openid-configuration',
|
||||
|
|
|
@ -52,10 +52,6 @@ export class ConnectionAPIController implements IConnectionAPIController {
|
|||
* "description": "SP for hoppscotch.io",
|
||||
* "clientID": "Xq8AJt3yYAxmXizsCWmUBDRiVP1iTC8Y/otnvFIMitk",
|
||||
* "clientSecret": "00e3e11a3426f97d8000000738300009130cd45419c5943",
|
||||
* "certs": {
|
||||
* "publicKey": "-----BEGIN CERTIFICATE-----.......-----END CERTIFICATE-----",
|
||||
* "privateKey": "-----BEGIN PRIVATE KEY-----......-----END PRIVATE KEY-----"
|
||||
* }
|
||||
* }
|
||||
* validationErrorsPost:
|
||||
* description: Please provide rawMetadata or encodedRawMetadata | Please provide a defaultRedirectUrl | Please provide redirectUrl | redirectUrl is invalid | Exceeded maximum number of allowed redirect urls | defaultRedirectUrl is invalid | Please provide tenant | Please provide product | Please provide a friendly name | Description should not exceed 100 characters | Strategy: xxxx not supported | Please provide the clientId from OpenID Provider | Please provide the clientSecret from OpenID Provider | Please provide the discoveryUrl for the OpenID Provider
|
||||
|
@ -422,9 +418,6 @@ export class ConnectionAPIController implements IConnectionAPIController {
|
|||
* idpMetadata:
|
||||
* type: object
|
||||
* description: SAML IdP metadata
|
||||
* certs:
|
||||
* type: object
|
||||
* description: Certs generated for SAML connection
|
||||
* oidcProvider:
|
||||
* type: object
|
||||
* description: OIDC IdP metadata
|
||||
|
@ -548,10 +541,6 @@ export class ConnectionAPIController implements IConnectionAPIController {
|
|||
* "description": "SP for hoppscotch.io",
|
||||
* "clientID": "Xq8AJt3yYAxmXizsCWmUBDRiVP1iTC8Y/otnvFIMitk",
|
||||
* "clientSecret": "00e3e11a3426f97d8000000738300009130cd45419c5943",
|
||||
* "certs": {
|
||||
* "publicKey": "-----BEGIN CERTIFICATE-----.......-----END CERTIFICATE-----",
|
||||
* "privateKey": "-----BEGIN PRIVATE KEY-----......-----END PRIVATE KEY-----"
|
||||
* }
|
||||
* }
|
||||
* '400':
|
||||
* $ref: '#/responses/400Get'
|
||||
|
|
|
@ -15,7 +15,6 @@ import {
|
|||
validateRedirectUrl,
|
||||
} from '../utils';
|
||||
import saml20 from '@boxyhq/saml20';
|
||||
import x509 from '../../saml/x509';
|
||||
import { JacksonError } from '../error';
|
||||
|
||||
const saml = {
|
||||
|
@ -76,14 +75,7 @@ const saml = {
|
|||
|
||||
record.clientID = dbutils.keyDigest(dbutils.keyFromParts(tenant, product, idpMetadata.entityID));
|
||||
|
||||
const certs = await x509.generate();
|
||||
|
||||
if (!certs) {
|
||||
throw new JacksonError('Error generating x509 certs');
|
||||
}
|
||||
|
||||
record.idpMetadata = idpMetadata;
|
||||
record.certs = certs;
|
||||
|
||||
const exists = await connectionStore.get(record.clientID);
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import { JacksonOption, SAMLConnection, SAMLResponsePayload, SLORequestParams, S
|
|||
import { JacksonError } from './error';
|
||||
import * as redirect from './oauth/redirect';
|
||||
import { IndexNames } from './utils';
|
||||
import { getDefaultCertificate } from '../saml/x509';
|
||||
|
||||
const deflateRawAsync = promisify(deflateRaw);
|
||||
|
||||
|
@ -50,9 +51,10 @@ export class LogoutController {
|
|||
|
||||
const {
|
||||
idpMetadata: { slo, provider },
|
||||
certs: { privateKey, publicKey },
|
||||
} = samlConnection;
|
||||
|
||||
const { privateKey, publicKey } = await getDefaultCertificate();
|
||||
|
||||
if ('redirectUrl' in slo === false && 'postUrl' in slo === false) {
|
||||
throw new JacksonError(`${provider} doesn't support SLO or disabled by IdP.`, 400);
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ import {
|
|||
loadJWSPrivateKey,
|
||||
isJWSKeyPairLoaded,
|
||||
} from './utils';
|
||||
import x509 from '../saml/x509';
|
||||
import { getDefaultCertificate } from '../saml/x509';
|
||||
|
||||
const deflateRawAsync = promisify(deflateRaw);
|
||||
|
||||
|
@ -354,41 +354,20 @@ export class OAuthController implements IOAuthController {
|
|||
};
|
||||
}
|
||||
|
||||
const cert = await getDefaultCertificate();
|
||||
|
||||
try {
|
||||
const { validTo } = new crypto.X509Certificate(connection.certs.publicKey);
|
||||
const isValidExpiry = validTo != 'Bad time value' && new Date(validTo) > new Date();
|
||||
if (!isValidExpiry) {
|
||||
const certs = await x509.generate();
|
||||
connection.certs = certs;
|
||||
if (certs) {
|
||||
await this.connectionStore.put(
|
||||
connection.clientID,
|
||||
connection,
|
||||
{
|
||||
// secondary index on entityID
|
||||
name: IndexNames.EntityID,
|
||||
value: connection.idpMetadata.entityID,
|
||||
},
|
||||
{
|
||||
// secondary index on tenant + product
|
||||
name: IndexNames.TenantProduct,
|
||||
value: dbutils.keyFromParts(connection.tenant, connection.product),
|
||||
}
|
||||
);
|
||||
} else {
|
||||
throw new Error('Error generating x509 certs');
|
||||
}
|
||||
}
|
||||
// We will get undefined or Space delimited, case sensitive list of ASCII string values in prompt
|
||||
// If login is one of the value in prompt we want to enable forceAuthn
|
||||
// Else use the saml connection forceAuthn value
|
||||
const promptOptions = prompt ? prompt.split(' ').filter((p) => p === 'login') : [];
|
||||
|
||||
samlReq = saml.request({
|
||||
ssoUrl,
|
||||
entityID: this.opts.samlAudience!,
|
||||
callbackUrl: this.opts.externalUrl + this.opts.samlPath,
|
||||
signingKey: connection.certs.privateKey,
|
||||
publicKey: connection.certs.publicKey,
|
||||
signingKey: cert.privateKey,
|
||||
publicKey: cert.publicKey,
|
||||
forceAuthn: promptOptions.length > 0 ? true : !!connection.forceAuthn,
|
||||
});
|
||||
} catch (err: unknown) {
|
||||
|
@ -402,6 +381,7 @@ export class OAuthController implements IOAuthController {
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
// OIDC Connection: Issuer discovery, openid-client init and extraction of authorization endpoint happens here
|
||||
let oidcCodeVerifier: string | undefined;
|
||||
if (connectionIsOIDC) {
|
||||
|
@ -616,10 +596,12 @@ export class OAuthController implements IOAuthController {
|
|||
throw new JacksonError('SAML connection not found.', 403);
|
||||
}
|
||||
|
||||
const { privateKey } = await getDefaultCertificate();
|
||||
|
||||
const validateOpts: Record<string, string> = {
|
||||
thumbprint: samlConnection.idpMetadata.thumbprint,
|
||||
audience: this.opts.samlAudience!,
|
||||
privateKey: samlConnection.certs.privateKey,
|
||||
privateKey,
|
||||
};
|
||||
|
||||
if (
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import type { JacksonOption } from '../typings';
|
||||
import { marked } from 'marked';
|
||||
|
||||
import saml20 from '@boxyhq/saml20';
|
||||
|
||||
// Service Provider SAML Configuration
|
||||
export class SPSAMLConfig {
|
||||
constructor(private opts: JacksonOption) {}
|
||||
constructor(private opts: JacksonOption, private getDefaultCertificate: any) {}
|
||||
|
||||
private get acsUrl(): string {
|
||||
return `${this.opts.externalUrl}${this.opts.samlPath}`;
|
||||
|
@ -25,25 +27,25 @@ export class SPSAMLConfig {
|
|||
return 'RSA-SHA256';
|
||||
}
|
||||
|
||||
private get assertionEncryption(): string {
|
||||
return 'Unencrypted';
|
||||
}
|
||||
|
||||
public get(): {
|
||||
public async get(): Promise<{
|
||||
acsUrl: string;
|
||||
entityId: string;
|
||||
response: string;
|
||||
assertionSignature: string;
|
||||
signatureAlgorithm: string;
|
||||
assertionEncryption: string;
|
||||
} {
|
||||
publicKey: string;
|
||||
publicKeyString: string;
|
||||
}> {
|
||||
const cert = await this.getDefaultCertificate();
|
||||
|
||||
return {
|
||||
acsUrl: this.acsUrl,
|
||||
entityId: this.entityId,
|
||||
response: this.responseSigned,
|
||||
assertionSignature: this.assertionSignature,
|
||||
signatureAlgorithm: this.signatureAlgorithm,
|
||||
assertionEncryption: this.assertionEncryption,
|
||||
publicKey: cert.publicKey,
|
||||
publicKeyString: saml20.stripCertHeaderAndFooter(cert.publicKey),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -53,8 +55,7 @@ export class SPSAMLConfig {
|
|||
.replace('{{entityId}}', this.entityId)
|
||||
.replace('{{responseSigned}}', this.responseSigned)
|
||||
.replace('{{assertionSignature}}', this.assertionSignature)
|
||||
.replace('{{signatureAlgorithm}}', this.signatureAlgorithm)
|
||||
.replace('{{assertionEncryption}}', this.assertionEncryption);
|
||||
.replace('{{signatureAlgorithm}}', this.signatureAlgorithm);
|
||||
}
|
||||
|
||||
public toHTML(): string {
|
||||
|
@ -83,5 +84,5 @@ Your Identity Provider (IdP) will ask for the following information while config
|
|||
{{signatureAlgorithm}}
|
||||
|
||||
**Assertion Encryption** <br />
|
||||
{{assertionEncryption}}
|
||||
If you want to encrypt the assertion, you can download our [public certificate](/.well-known/saml.cer). Otherwise select the 'Unencrypted' option.
|
||||
`;
|
||||
|
|
|
@ -20,7 +20,7 @@ class Redis implements DatabaseDriver {
|
|||
}
|
||||
|
||||
this.client = redis.createClient(opts);
|
||||
this.client.on('error', (err: any) => console.log('Redis Client Error', err));
|
||||
this.client.on('error', (err: any) => console.info('Redis Client Error', err));
|
||||
|
||||
await this.client.connect();
|
||||
|
||||
|
|
|
@ -102,7 +102,7 @@ class Sql implements DatabaseDriver {
|
|||
|
||||
this.timerId = setTimeout(this.ttlCleanup, this.options.ttl! * 1000);
|
||||
} else {
|
||||
console.log(
|
||||
console.warn(
|
||||
'Warning: ttl cleanup not enabled, set both "ttl" and "cleanupLimit" options to enable it!'
|
||||
);
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import { LogoutController } from './controller/logout';
|
|||
import initDirectorySync from './directory-sync';
|
||||
import { OidcDiscoveryController } from './controller/oidc-discovery';
|
||||
import { SPSAMLConfig } from './controller/sp-config';
|
||||
import * as x509 from './saml/x509';
|
||||
|
||||
const defaultOpts = (opts: JacksonOption): JacksonOption => {
|
||||
const newOpts = {
|
||||
|
@ -67,12 +68,16 @@ export const controllers = async (
|
|||
const codeStore = db.store('oauth:code', opts.db.ttl);
|
||||
const tokenStore = db.store('oauth:token', opts.db.ttl);
|
||||
const healthCheckStore = db.store('_health:check');
|
||||
const certificateStore = db.store('x509:certificates');
|
||||
|
||||
const connectionAPIController = new ConnectionAPIController({ connectionStore, opts });
|
||||
const adminController = new AdminController({ connectionStore });
|
||||
const healthCheckController = new HealthCheckController({ healthCheckStore });
|
||||
await healthCheckController.init();
|
||||
|
||||
// Create default certificate if it doesn't exist.
|
||||
await x509.init(certificateStore);
|
||||
|
||||
const oauthController = new OAuthController({
|
||||
connectionStore,
|
||||
sessionStore,
|
||||
|
@ -91,7 +96,7 @@ export const controllers = async (
|
|||
|
||||
const oidcDiscoveryController = new OidcDiscoveryController({ opts });
|
||||
|
||||
const spConfig = new SPSAMLConfig(opts);
|
||||
const spConfig = new SPSAMLConfig(opts, x509.getDefaultCertificate);
|
||||
|
||||
// write pre-loaded connections if present
|
||||
const preLoadedConnection = opts.preLoadedConnection || opts.preLoadedConfig;
|
||||
|
@ -105,13 +110,13 @@ export const controllers = async (
|
|||
await connectionAPIController.createSAMLConnection(connection);
|
||||
}
|
||||
|
||||
console.log(`loaded connection for tenant "${connection.tenant}" and product "${connection.product}"`);
|
||||
console.info(`loaded connection for tenant "${connection.tenant}" and product "${connection.product}"`);
|
||||
}
|
||||
}
|
||||
|
||||
const type = opts.db.engine === 'sql' && opts.db.type ? ' Type: ' + opts.db.type : '';
|
||||
|
||||
console.log(`Using engine: ${opts.db.engine}.${type}`);
|
||||
console.info(`Using engine: ${opts.db.engine}.${type}`);
|
||||
|
||||
return {
|
||||
spConfig,
|
||||
|
|
|
@ -1,19 +1,35 @@
|
|||
import * as forge from 'node-forge';
|
||||
import crypto from 'crypto';
|
||||
|
||||
import type { Storable } from '../typings';
|
||||
|
||||
const pki = forge.pki;
|
||||
const generate = () => {
|
||||
let certificateStore: Storable;
|
||||
let cachedCertificate: { publicKey: string; privateKey: string };
|
||||
|
||||
export const init = async (store: Storable) => {
|
||||
certificateStore = store;
|
||||
|
||||
return await getDefaultCertificate();
|
||||
};
|
||||
|
||||
export const generateCertificate = () => {
|
||||
const today = new Date();
|
||||
const keys = pki.rsa.generateKeyPair(2048);
|
||||
const cert = pki.createCertificate();
|
||||
|
||||
cert.publicKey = keys.publicKey;
|
||||
cert.serialNumber = '01';
|
||||
cert.validity.notBefore = new Date();
|
||||
cert.validity.notAfter = new Date(today.setFullYear(today.getFullYear() + 10));
|
||||
cert.validity.notAfter = new Date(today.setFullYear(today.getFullYear() + 30));
|
||||
|
||||
const attrs = [
|
||||
{
|
||||
name: 'commonName',
|
||||
value: 'BoxyHQ Jackson',
|
||||
},
|
||||
];
|
||||
|
||||
cert.setSubject(attrs);
|
||||
cert.setIssuer(attrs);
|
||||
cert.setExtensions([
|
||||
|
@ -30,6 +46,7 @@ const generate = () => {
|
|||
dataEncipherment: false,
|
||||
},
|
||||
]);
|
||||
|
||||
// self-sign certificate
|
||||
cert.sign(keys.privateKey, forge.md.sha256.create());
|
||||
|
||||
|
@ -39,6 +56,32 @@ const generate = () => {
|
|||
};
|
||||
};
|
||||
|
||||
export default {
|
||||
generate,
|
||||
export const getDefaultCertificate = async (): Promise<{ publicKey: string; privateKey: string }> => {
|
||||
if (cachedCertificate && !(await isCertificateExpired(cachedCertificate.publicKey))) {
|
||||
return cachedCertificate;
|
||||
}
|
||||
|
||||
if (!certificateStore) {
|
||||
throw new Error('Certificate store not initialized');
|
||||
}
|
||||
|
||||
cachedCertificate = await certificateStore.get('default');
|
||||
|
||||
// If certificate is expired let it drop through so it creates a new cert
|
||||
if (cachedCertificate && !(await isCertificateExpired(cachedCertificate.publicKey))) {
|
||||
return cachedCertificate;
|
||||
}
|
||||
|
||||
// If default certificate is not found or has expired, create one and store it.
|
||||
cachedCertificate = generateCertificate();
|
||||
|
||||
await certificateStore.put('default', cachedCertificate);
|
||||
|
||||
return cachedCertificate;
|
||||
};
|
||||
|
||||
const isCertificateExpired = async (publicKey: string) => {
|
||||
const { validTo } = new crypto.X509Certificate(publicKey);
|
||||
|
||||
return !(validTo != 'Bad time value' && new Date(validTo) > new Date());
|
||||
};
|
||||
|
|
|
@ -47,10 +47,6 @@ export interface SAMLSSORecord extends SAMLSSOConnection {
|
|||
thumbprint?: string;
|
||||
validTo?: string;
|
||||
};
|
||||
certs: {
|
||||
privateKey: string;
|
||||
publicKey: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface OIDCSSORecord extends SSOConnection {
|
||||
|
@ -349,10 +345,6 @@ interface Metadata {
|
|||
|
||||
export interface SAMLConnection {
|
||||
idpMetadata: Metadata;
|
||||
certs: {
|
||||
privateKey: string;
|
||||
publicKey: string;
|
||||
};
|
||||
defaultRedirectUrl: string;
|
||||
}
|
||||
|
||||
|
@ -384,14 +376,15 @@ export type OIDCErrorCodes =
|
|||
| 'registration_not_supported';
|
||||
|
||||
export interface ISPSAMLConfig {
|
||||
get(): {
|
||||
get(): Promise<{
|
||||
acsUrl: string;
|
||||
entityId: string;
|
||||
response: string;
|
||||
assertionSignature: string;
|
||||
signatureAlgorithm: string;
|
||||
assertionEncryption: string;
|
||||
};
|
||||
publicKey: string;
|
||||
publicKeyString: string;
|
||||
}>;
|
||||
toMarkdown(): string;
|
||||
toHTML(): string;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "jackson",
|
||||
"version": "1.3.5",
|
||||
"version": "1.3.6",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "jackson",
|
||||
"version": "1.3.5",
|
||||
"version": "1.3.6",
|
||||
"license": "Apache 2.0",
|
||||
"dependencies": {
|
||||
"@boxyhq/saml-jackson": "file:./npm",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "jackson",
|
||||
"version": "1.3.5",
|
||||
"version": "1.3.6",
|
||||
"private": true,
|
||||
"description": "SAML 2.0 service",
|
||||
"keywords": [
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import jackson from '@lib/jackson';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (req.method !== 'GET') {
|
||||
throw { message: 'Method not allowed', statusCode: 405 };
|
||||
}
|
||||
|
||||
const { spConfig } = await jackson();
|
||||
const config = await spConfig.get();
|
||||
|
||||
res.status(200).setHeader('Content-Type', 'application/x-x509-ca-cert').send(config.publicKey);
|
||||
}
|
|
@ -5,14 +5,12 @@ import xmlbuilder from 'xmlbuilder';
|
|||
const createSSOMetadataXML = async ({
|
||||
entityId,
|
||||
acsUrl,
|
||||
}: //certificate,
|
||||
{
|
||||
publicKeyString,
|
||||
}: {
|
||||
entityId: string;
|
||||
acsUrl: string;
|
||||
//certificate: string;
|
||||
publicKeyString: string;
|
||||
}): Promise<string> => {
|
||||
// certificate = saml.stripCertHeaderAndFooter(certificate);
|
||||
|
||||
const today = new Date();
|
||||
|
||||
const nodes = {
|
||||
|
@ -23,17 +21,33 @@ const createSSOMetadataXML = async ({
|
|||
'md:SPSSODescriptor': {
|
||||
//'@WantAuthnRequestsSigned': true,
|
||||
'@protocolSupportEnumeration': 'urn:oasis:names:tc:SAML:2.0:protocol',
|
||||
// 'md:KeyDescriptor': {
|
||||
// '@use': 'signing',
|
||||
// 'ds:KeyInfo': {
|
||||
// '@xmlns:ds': 'http://www.w3.org/2000/09/xmldsig#',
|
||||
// 'ds:X509Data': {
|
||||
// 'ds:X509Certificate': {
|
||||
// '#text': certificate,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
'md:KeyDescriptor': [
|
||||
{
|
||||
'@use': 'signing',
|
||||
'ds:KeyInfo': {
|
||||
'@xmlns:ds': 'http://www.w3.org/2000/09/xmldsig#',
|
||||
'ds:X509Data': {
|
||||
'ds:X509Certificate': {
|
||||
'#text': publicKeyString,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
'@use': 'encryption',
|
||||
'ds:KeyInfo': {
|
||||
'@xmlns:ds': 'http://www.w3.org/2000/09/xmldsig#',
|
||||
'ds:X509Data': {
|
||||
'ds:X509Certificate': {
|
||||
'#text': publicKeyString,
|
||||
},
|
||||
},
|
||||
},
|
||||
'md:EncryptionMethod': {
|
||||
'@Algorithm': 'http://www.w3.org/2001/04/xmlenc#aes256-cbc',
|
||||
},
|
||||
},
|
||||
],
|
||||
'md:NameIDFormat': {
|
||||
'#text': 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
|
||||
},
|
||||
|
@ -55,8 +69,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
}
|
||||
const { spConfig } = await jackson();
|
||||
|
||||
const config = spConfig.get();
|
||||
const config = await spConfig.get();
|
||||
|
||||
const xml = await createSSOMetadataXML({ entityId: config.entityId, acsUrl: config.acsUrl });
|
||||
const xml = await createSSOMetadataXML({
|
||||
entityId: config.entityId,
|
||||
acsUrl: config.acsUrl,
|
||||
publicKeyString: config.publicKeyString,
|
||||
});
|
||||
res.status(200).setHeader('Content-Type', 'text/xml').send(xml);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"info": {
|
||||
"title": "SAML Jackson API",
|
||||
"version": "1.3.2",
|
||||
"version": "1.3.6",
|
||||
"description": "This is the API documentation for SAML Jackson service.",
|
||||
"termsOfService": "https://boxyhq.com/terms.html",
|
||||
"contact": {
|
||||
|
@ -191,11 +191,7 @@
|
|||
"name": "Hoppscotch-SP",
|
||||
"description": "SP for hoppscotch.io",
|
||||
"clientID": "Xq8AJt3yYAxmXizsCWmUBDRiVP1iTC8Y/otnvFIMitk",
|
||||
"clientSecret": "00e3e11a3426f97d8000000738300009130cd45419c5943",
|
||||
"certs": {
|
||||
"publicKey": "-----BEGIN CERTIFICATE-----.......-----END CERTIFICATE-----",
|
||||
"privateKey": "-----BEGIN PRIVATE KEY-----......-----END PRIVATE KEY-----"
|
||||
}
|
||||
"clientSecret": "00e3e11a3426f97d8000000738300009130cd45419c5943"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -602,11 +598,7 @@
|
|||
"name": "Hoppscotch-SP",
|
||||
"description": "SP for hoppscotch.io",
|
||||
"clientID": "Xq8AJt3yYAxmXizsCWmUBDRiVP1iTC8Y/otnvFIMitk",
|
||||
"clientSecret": "00e3e11a3426f97d8000000738300009130cd45419c5943",
|
||||
"certs": {
|
||||
"publicKey": "-----BEGIN CERTIFICATE-----.......-----END CERTIFICATE-----",
|
||||
"privateKey": "-----BEGIN PRIVATE KEY-----......-----END PRIVATE KEY-----"
|
||||
}
|
||||
"clientSecret": "00e3e11a3426f97d8000000738300009130cd45419c5943"
|
||||
},
|
||||
"properties": {
|
||||
"clientID": {
|
||||
|
@ -645,10 +637,6 @@
|
|||
"type": "object",
|
||||
"description": "SAML IdP metadata"
|
||||
},
|
||||
"certs": {
|
||||
"type": "object",
|
||||
"description": "Certs generated for SAML connection"
|
||||
},
|
||||
"oidcProvider": {
|
||||
"type": "object",
|
||||
"description": "OIDC IdP metadata"
|
||||
|
|
Loading…
Reference in New Issue