mirror of https://github.com/boxyhq/jackson.git
feat: Stats route changes to return count of setup link & saml federations apps (#2627)
* feat: stats route updates to respond count of setup link & saml federations apps * chore: Remove unused getCountByProductService method from SetupLinkController * feat: Add validation for development mode connection limits * chore: Update import path for validateDevelopmentModeLimits in directory-sync and sso-connection APIs * refactor: update development mode limits validation in directory-sync and connections APIs * feat: Update development mode limits validation in directory-sync and connections APIs
This commit is contained in:
parent
1eb2147802
commit
b98ccc68bc
|
@ -3,6 +3,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
|
|||
import jackson from '@lib/jackson';
|
||||
import { defaultHandler } from '@lib/api';
|
||||
import { parsePaginateApiParams } from '@lib/utils';
|
||||
import { validateDevelopmentModeLimits } from '@lib/development-mode';
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
await defaultHandler(req, res, {
|
||||
|
@ -15,6 +16,12 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||
const handlePOST = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const { samlFederatedController } = await jackson();
|
||||
|
||||
await validateDevelopmentModeLimits(
|
||||
req.body.product,
|
||||
'samlFederation',
|
||||
'Maximum number of federation apps reached'
|
||||
);
|
||||
|
||||
const app = await samlFederatedController.app.create(req.body);
|
||||
|
||||
res.status(201).json({ data: app });
|
||||
|
|
|
@ -2,36 +2,28 @@ import { AppRequestParams } from '@boxyhq/saml-jackson';
|
|||
import { NextApiRequest, NextApiResponse } from 'next';
|
||||
|
||||
import jackson from '@lib/jackson';
|
||||
import { validateDevelopmentModeLimits } from '@lib/development-mode';
|
||||
import { defaultHandler } from '@lib/api';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
switch (req.method) {
|
||||
case 'POST':
|
||||
await handlePOST(req, res);
|
||||
break;
|
||||
case 'GET':
|
||||
await handleGET(req, res);
|
||||
break;
|
||||
case 'PATCH':
|
||||
await handlePATCH(req, res);
|
||||
break;
|
||||
case 'DELETE':
|
||||
await handleDELETE(req, res);
|
||||
break;
|
||||
default:
|
||||
res.setHeader('Allow', 'POST, GET, PATCH, DELETE');
|
||||
res.status(405).json({ error: { message: `Method ${req.method} Not Allowed` } });
|
||||
}
|
||||
} catch (error: any) {
|
||||
const { message, statusCode = 500 } = error;
|
||||
res.status(statusCode).json({ error: { message } });
|
||||
}
|
||||
await defaultHandler(req, res, {
|
||||
POST: handlePOST,
|
||||
GET: handleGET,
|
||||
PATCH: handlePATCH,
|
||||
DELETE: handleDELETE,
|
||||
});
|
||||
}
|
||||
|
||||
// Create a SAML federated app
|
||||
const handlePOST = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const { samlFederatedController } = await jackson();
|
||||
|
||||
await validateDevelopmentModeLimits(
|
||||
req.body.product,
|
||||
'samlFederation',
|
||||
'Maximum number of federation apps reached'
|
||||
);
|
||||
|
||||
const app = await samlFederatedController.app.create(req.body);
|
||||
|
||||
res.status(201).json({ data: app });
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
import jackson from './jackson';
|
||||
import { IndexNames } from 'npm/src/controller/utils';
|
||||
|
||||
type Module = 'sso' | 'dsync' | 'samlFederation';
|
||||
|
||||
export const validateDevelopmentModeLimits = async (
|
||||
productId: string,
|
||||
type: Module,
|
||||
message: string = 'Maximum number of connections reached'
|
||||
) => {
|
||||
if (productId) {
|
||||
const { productController, connectionAPIController, directorySyncController, samlFederatedController } =
|
||||
await jackson();
|
||||
|
||||
const getController = async (type: Module) => {
|
||||
switch (type) {
|
||||
case 'sso':
|
||||
return connectionAPIController;
|
||||
case 'dsync':
|
||||
return directorySyncController.directories;
|
||||
case 'samlFederation':
|
||||
return samlFederatedController.app;
|
||||
default:
|
||||
return {
|
||||
getCount: () => null,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const product = await productController.get(productId);
|
||||
if (product?.development) {
|
||||
const controller = await getController(type);
|
||||
const count = await controller.getCount({
|
||||
name: IndexNames.Product,
|
||||
value: productId,
|
||||
});
|
||||
if (count) {
|
||||
if (count >= 3) {
|
||||
throw { message, statusCode: 400 };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
|
@ -8,6 +8,7 @@ import type {
|
|||
Records,
|
||||
GetByProductParams,
|
||||
AppRequestParams,
|
||||
Index,
|
||||
} from '../../typings';
|
||||
import { fedAppID, clientIDFederatedPrefix } from '../../controller/utils';
|
||||
import { JacksonError } from '../../controller/error';
|
||||
|
@ -629,4 +630,8 @@ export class App {
|
|||
x509cert: publicKey,
|
||||
};
|
||||
}
|
||||
|
||||
public async getCount(idx?: Index) {
|
||||
return await this.store.getCount(idx);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -622,4 +622,5 @@ export interface ProductConfig {
|
|||
faviconUrl: string | null;
|
||||
companyName: string | null;
|
||||
ory: OryConfig | null;
|
||||
development?: boolean;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import { oidcMetadataParse, parsePaginateApiParams, strategyChecker } from '@lib
|
|||
import { adminPortalSSODefaults } from '@lib/env';
|
||||
import { defaultHandler } from '@lib/api';
|
||||
import { ApiError } from '@lib/error';
|
||||
import { validateDevelopmentModeLimits } from '@lib/development-mode';
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
await defaultHandler(req, res, {
|
||||
|
@ -55,6 +56,8 @@ const handlePOST = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||
|
||||
const { isSAML, isOIDC } = strategyChecker(req);
|
||||
|
||||
await validateDevelopmentModeLimits(req.body.product, 'sso');
|
||||
|
||||
if (!isSAML && !isOIDC) {
|
||||
throw new ApiError('Missing SSO connection params', 400);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import jackson from '@lib/jackson';
|
|||
import { defaultHandler } from '@lib/api';
|
||||
import { ApiError } from '@lib/error';
|
||||
import { parsePaginateApiParams } from '@lib/utils';
|
||||
import { validateDevelopmentModeLimits } from '@lib/development-mode';
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
await defaultHandler(req, res, {
|
||||
|
@ -17,6 +18,8 @@ const handlePOST = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||
|
||||
const { name, tenant, product, type, webhook_url, webhook_secret, google_domain } = req.body;
|
||||
|
||||
await validateDevelopmentModeLimits(product, 'dsync');
|
||||
|
||||
const { data, error } = await directorySyncController.directories.create({
|
||||
name,
|
||||
tenant,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import type { SetupLink } from '@boxyhq/saml-jackson';
|
||||
import jackson from '@lib/jackson';
|
||||
import { validateDevelopmentModeLimits } from '@lib/development-mode';
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const { setupLinkController } = await jackson();
|
||||
|
@ -33,6 +34,8 @@ const handlePOST = async (req: NextApiRequest, res: NextApiResponse, setupLink:
|
|||
|
||||
const { type, google_domain } = req.body;
|
||||
|
||||
await validateDevelopmentModeLimits(setupLink.product, 'dsync');
|
||||
|
||||
const directory = {
|
||||
type,
|
||||
google_domain,
|
||||
|
|
|
@ -1,39 +1,23 @@
|
|||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import jackson from '@lib/jackson';
|
||||
import { oidcMetadataParse, strategyChecker } from '@lib/utils';
|
||||
import type { SetupLink } from '@boxyhq/saml-jackson';
|
||||
import { validateDevelopmentModeLimits } from '@lib/development-mode';
|
||||
import { defaultHandler } from '@lib/api';
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const { setupLinkController } = await jackson();
|
||||
|
||||
const { method } = req;
|
||||
const { token } = req.query as { token: string };
|
||||
|
||||
try {
|
||||
const setupLink = await setupLinkController.getByToken(token);
|
||||
|
||||
switch (method) {
|
||||
case 'GET':
|
||||
return await handleGET(req, res, setupLink);
|
||||
case 'POST':
|
||||
return await handlePOST(req, res, setupLink);
|
||||
case 'PATCH':
|
||||
return await handlePATCH(req, res);
|
||||
case 'DELETE':
|
||||
return await handleDELETE(req, res);
|
||||
default:
|
||||
res.setHeader('Allow', 'GET, POST, PATCH, DELETE');
|
||||
res.status(405).json({ error: { message: `Method ${method} Not Allowed` } });
|
||||
}
|
||||
} catch (error: any) {
|
||||
const { message, statusCode = 500 } = error;
|
||||
|
||||
return res.status(statusCode).json({ error: { message } });
|
||||
}
|
||||
await defaultHandler(req, res, {
|
||||
GET: handleGET,
|
||||
POST: handlePOST,
|
||||
PATCH: handlePATCH,
|
||||
DELETE: handleDELETE,
|
||||
});
|
||||
};
|
||||
|
||||
const handleGET = async (req: NextApiRequest, res: NextApiResponse, setupLink: SetupLink) => {
|
||||
const { connectionAPIController } = await jackson();
|
||||
const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const { token } = req.query as { token: string };
|
||||
const { connectionAPIController, setupLinkController } = await jackson();
|
||||
|
||||
const setupLink = await setupLinkController.getByToken(token);
|
||||
|
||||
const connections = await connectionAPIController.getConnections({
|
||||
tenant: setupLink.tenant,
|
||||
|
@ -68,14 +52,19 @@ const handleGET = async (req: NextApiRequest, res: NextApiResponse, setupLink: S
|
|||
res.json(_connections);
|
||||
};
|
||||
|
||||
const handlePOST = async (req: NextApiRequest, res: NextApiResponse, setupLink: SetupLink) => {
|
||||
const { connectionAPIController } = await jackson();
|
||||
const handlePOST = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const { token } = req.query as { token: string };
|
||||
const { connectionAPIController, setupLinkController } = await jackson();
|
||||
|
||||
const setupLink = await setupLinkController.getByToken(token);
|
||||
|
||||
const body = {
|
||||
...req.body,
|
||||
...setupLink,
|
||||
};
|
||||
|
||||
await validateDevelopmentModeLimits(body.product, 'sso');
|
||||
|
||||
const { isSAML, isOIDC } = strategyChecker(req);
|
||||
|
||||
if (isSAML) {
|
||||
|
|
|
@ -1,18 +1,13 @@
|
|||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import jackson from '@lib/jackson';
|
||||
import { validateDevelopmentModeLimits } from '@lib/development-mode';
|
||||
import { defaultHandler } from '@lib/api';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { method } = req;
|
||||
|
||||
switch (method) {
|
||||
case 'GET':
|
||||
return await handleGET(req, res);
|
||||
case 'POST':
|
||||
return await handlePOST(req, res);
|
||||
default:
|
||||
res.setHeader('Allow', 'GET, POST');
|
||||
res.status(405).json({ error: { message: `Method ${method} Not Allowed` } });
|
||||
}
|
||||
await defaultHandler(req, res, {
|
||||
GET: handleGET,
|
||||
POST: handlePOST,
|
||||
});
|
||||
}
|
||||
|
||||
// Get the configuration
|
||||
|
@ -41,6 +36,8 @@ const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||
const handlePOST = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const { directorySyncController } = await jackson();
|
||||
|
||||
await validateDevelopmentModeLimits(req.body.product, 'dsync');
|
||||
|
||||
const { data, error } = await directorySyncController.directories.create(req.body);
|
||||
|
||||
if (error) {
|
||||
|
|
|
@ -2,29 +2,16 @@ import jackson from '@lib/jackson';
|
|||
import { oidcMetadataParse, strategyChecker } from '@lib/utils';
|
||||
import { NextApiRequest, NextApiResponse } from 'next';
|
||||
import type { DelConnectionsQuery } from '@boxyhq/saml-jackson';
|
||||
import { validateDevelopmentModeLimits } from '@lib/development-mode';
|
||||
import { defaultHandler } from '@lib/api';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { method } = req;
|
||||
|
||||
try {
|
||||
switch (method) {
|
||||
case 'GET':
|
||||
return await handleGET(req, res);
|
||||
case 'POST':
|
||||
return await handlePOST(req, res);
|
||||
case 'PATCH':
|
||||
return await handlePATCH(req, res);
|
||||
case 'DELETE':
|
||||
return await handleDELETE(req, res);
|
||||
default:
|
||||
res.setHeader('Allow', 'GET, POST, PATCH, DELETE');
|
||||
res.status(405).json({ error: { message: `Method ${method} Not Allowed` } });
|
||||
}
|
||||
} catch (error: any) {
|
||||
const { message, statusCode = 500 } = error;
|
||||
|
||||
return res.status(statusCode).json({ error: { message } });
|
||||
}
|
||||
await defaultHandler(req, res, {
|
||||
GET: handleGET,
|
||||
POST: handlePOST,
|
||||
PATCH: handlePATCH,
|
||||
DELETE: handleDELETE,
|
||||
});
|
||||
}
|
||||
|
||||
// Get all connections
|
||||
|
@ -52,6 +39,8 @@ const handlePOST = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||
throw { message: 'Missing SSO connection params', statusCode: 400 };
|
||||
}
|
||||
|
||||
await validateDevelopmentModeLimits(req.body.product, 'sso');
|
||||
|
||||
// Create SAML connection
|
||||
if (isSAML) {
|
||||
const connection = await connectionAPIController.createSAMLConnection(req.body);
|
||||
|
|
|
@ -21,11 +21,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
}
|
||||
|
||||
const handlePOST = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const { connectionAPIController, directorySyncController } = await jackson();
|
||||
const { connectionAPIController, directorySyncController, samlFederatedController } = await jackson();
|
||||
|
||||
// Products must be an array of strings
|
||||
const products = req.body.products as string[];
|
||||
const type = req.body.type ? (req.body.type as 'sso' | 'dsync') : undefined;
|
||||
const type = req.body.type ? (req.body.type as 'sso' | 'dsync' | 'samlFederation') : undefined;
|
||||
|
||||
// Validate products
|
||||
if (!products) {
|
||||
|
@ -36,9 +36,9 @@ const handlePOST = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||
throw { message: 'Products must not exceed 50', statusCode: 400 };
|
||||
} else {
|
||||
// Get counts for product
|
||||
// If type is not provided, get counts for both sso and dsync
|
||||
let sso_connections_count = 0;
|
||||
let dsync_connections_count = 0;
|
||||
let saml_federation_count = 0;
|
||||
|
||||
for (const product of products) {
|
||||
if (product) {
|
||||
|
@ -54,6 +54,14 @@ const handlePOST = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||
});
|
||||
dsync_connections_count += count || 0;
|
||||
}
|
||||
|
||||
if (!type || type === 'samlFederation') {
|
||||
const count = await samlFederatedController.app.getCount({
|
||||
name: IndexNames.Product,
|
||||
value: product,
|
||||
});
|
||||
saml_federation_count += count || 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,6 +69,7 @@ const handlePOST = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||
data: {
|
||||
sso_connections: sso_connections_count,
|
||||
dsync_connections: dsync_connections_count,
|
||||
saml_federation: saml_federation_count,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue