Request handler middleware for API routes (#2403)

* Add error class and update API handlers

* Refactor admin API handlers

* Fix error constructor parameter order

* Add defaultHandler to API endpoints

* Keep the handler name

* Fix error status code in defaultHandler and ApiError

* Fix the status code

* Improve conditional logic

* Small tweak

---------

Co-authored-by: Aswin V <vaswin91@gmail.com>
This commit is contained in:
Kiran K 2024-03-14 20:21:16 +05:30 committed by GitHub
parent 64dc25f694
commit f7cf763cbc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 287 additions and 504 deletions

View File

@ -1,25 +1,13 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import jackson from '@lib/jackson';
import { defaultHandler } from '@lib/api';
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const { method } = req;
try {
switch (method) {
case 'POST':
return await handlePOST(req, res);
case 'GET':
return await handleGET(req, res);
default:
res.setHeader('Allow', 'POST, GET');
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,
});
};
const handlePOST = async (req: NextApiRequest, res: NextApiResponse) => {
@ -27,7 +15,7 @@ const handlePOST = async (req: NextApiRequest, res: NextApiResponse) => {
const { logoUrl, faviconUrl, companyName, primaryColor } = req.body;
return res.json({
res.json({
data: await brandingController.update({ logoUrl, faviconUrl, companyName, primaryColor }),
});
};
@ -35,7 +23,7 @@ const handlePOST = async (req: NextApiRequest, res: NextApiResponse) => {
const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
const { brandingController } = await jackson();
return res.json({ data: await brandingController.get() });
res.json({ data: await brandingController.get() });
};
export default handler;

View File

@ -1,29 +1,14 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import jackson from '@lib/jackson';
import { defaultHandler } from '@lib/api';
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const { method } = req;
try {
switch (method) {
case 'GET':
return await handleGET(req, res);
case 'PATCH':
return await handlePATCH(req, res);
case 'DELETE':
return await handleDELETE(req, res);
default:
res.setHeader('Allow', 'GET, PUT, DELETE');
res.status(405).json({ data: null, 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,
PATCH: handlePATCH,
DELETE: handleDELETE,
});
};
// Get Identity Federation app by id
@ -35,7 +20,7 @@ const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
const app = await samlFederatedController.app.get({ id });
const metadata = await samlFederatedController.app.getMetadata();
return res.status(200).json({
res.json({
data: {
...app,
metadata,
@ -49,7 +34,7 @@ const handlePATCH = async (req: NextApiRequest, res: NextApiResponse) => {
const updatedApp = await samlFederatedController.app.update(req.body);
return res.status(200).json({ data: updatedApp });
res.json({ data: updatedApp });
};
// Delete the Identity Federation app
@ -60,7 +45,7 @@ const handleDELETE = async (req: NextApiRequest, res: NextApiResponse) => {
await samlFederatedController.app.delete({ id });
return res.status(200).json({ data: {} });
res.json({ data: null });
};
export default handler;

View File

@ -1,26 +1,14 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import jackson from '@lib/jackson';
import { defaultHandler } from '@lib/api';
import { parsePaginateApiParams } from '@lib/utils';
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const { method } = req;
try {
switch (method) {
case 'POST':
return await handlePOST(req, res);
case 'GET':
return await handleGET(req, res);
default:
res.setHeader('Allow', 'POST, GET');
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,
});
};
// Create new Identity Federation app
@ -29,7 +17,7 @@ const handlePOST = async (req: NextApiRequest, res: NextApiResponse) => {
const app = await samlFederatedController.app.create(req.body);
return res.status(201).json({ data: app });
res.status(201).json({ data: app });
};
// Get Identity Federation apps
@ -48,7 +36,7 @@ const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
res.setHeader('jackson-pagetoken', apps.pageToken);
}
return res.json({ data: apps.data });
res.json({ data: apps.data });
};
export default handler;

32
lib/api/default.ts Normal file
View File

@ -0,0 +1,32 @@
import { ApiError } from '../error';
import type { NextApiRequest, NextApiResponse } from 'next';
type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
type Handlers = {
[method in HTTPMethod]?: (req: NextApiRequest, res: NextApiResponse) => Promise<void>;
};
export const defaultHandler = async (req: NextApiRequest, res: NextApiResponse, handlers: Handlers) => {
try {
// Get the handler for the request
const handler = handlers[req.method as HTTPMethod];
const allowedMethods = Object.keys(handlers).join(', ');
if (!handler) {
res.setHeader('Allow', allowedMethods);
throw new ApiError(`Method ${req.method} not allowed.`, 405);
}
// Call the handler
await handler(req, res);
return;
} catch (error: any) {
const message = error.message || 'An server error occurred.';
const status = error.statusCode || 500;
console.error(`${req.method} ${req.url} - ${status} - ${message}`);
res.status(status).json({ error: { message } });
}
};

1
lib/api/index.ts Normal file
View File

@ -0,0 +1 @@
export { defaultHandler } from './default';

8
lib/error.ts Normal file
View File

@ -0,0 +1,8 @@
export class ApiError extends Error {
statusCode: number;
constructor(message: string, statusCode: number) {
super(message);
this.statusCode = statusCode;
}
}

View File

@ -1,17 +1,13 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import jackson from '@lib/jackson';
import { defaultHandler } from '@lib/api';
import { ApiError } from '@lib/error';
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const { method } = req;
switch (method) {
case 'GET':
return await handleGET(req, res);
default:
res.setHeader('Allow', 'GET');
res.status(405).json({ error: { message: `Method ${method} Not Allowed` } });
}
await defaultHandler(req, res, {
GET: handleGET,
});
};
// Get connection by clientID
@ -22,15 +18,13 @@ const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
clientId: string;
};
try {
const connections = await connectionAPIController.getConnections({ clientID: clientId });
const connections = await connectionAPIController.getConnections({ clientID: clientId });
return res.json({ data: connections[0] });
} catch (error: any) {
const { message, statusCode = 500 } = error;
return res.status(statusCode).json({ error: { message } });
if (!connections || connections.length === 0) {
throw new ApiError('Connection not found', 404);
}
res.json({ data: connections[0] });
};
export default handler;

View File

@ -1,16 +1,11 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import jackson from '@lib/jackson';
import { defaultHandler } from '@lib/api';
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const { method } = req;
switch (method) {
case 'GET':
return await handleGET(req, res);
default:
res.setHeader('Allow', 'GET');
res.status(405).json({ error: { message: `Method ${method} Not Allowed` } });
}
await defaultHandler(req, res, {
GET: handleGET,
});
};
const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
@ -21,7 +16,7 @@ const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
product: req.body.product,
});
return res.json({ data: { idpEntityID } });
res.json({ data: { idpEntityID } });
};
export default handler;

View File

@ -3,23 +3,16 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import jackson from '@lib/jackson';
import { oidcMetadataParse, parsePaginateApiParams, strategyChecker } from '@lib/utils';
import { adminPortalSSODefaults } from '@lib/env';
import { defaultHandler } from '@lib/api';
import { ApiError } from '@lib/error';
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const { method } = req;
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` } });
}
await defaultHandler(req, res, {
GET: handleGET,
POST: handlePOST,
PATCH: handlePATCH,
DELETE: handleDELETE,
});
};
// Get all connections
@ -52,7 +45,8 @@ const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
if (paginatedConnectionList.pageToken) {
res.setHeader('jackson-pagetoken', paginatedConnectionList.pageToken);
}
return res.json(connections);
res.json(connections);
};
// Create a new connection
@ -62,27 +56,19 @@ const handlePOST = async (req: NextApiRequest, res: NextApiResponse) => {
const { isSAML, isOIDC } = strategyChecker(req);
if (!isSAML && !isOIDC) {
return res.status(400).json({ error: { message: 'Missing SSO connection params' } });
throw new ApiError('Missing SSO connection params', 400);
}
try {
// Create SAML connection
if (isSAML) {
const connection = await connectionAPIController.createSAMLConnection(req.body);
// Create SAML connection
if (isSAML) {
const connection = await connectionAPIController.createSAMLConnection(req.body);
res.status(201).json({ data: connection });
}
return res.status(201).json({ data: connection });
}
// Create OIDC connection
if (isOIDC) {
const connection = await connectionAPIController.createOIDCConnection(oidcMetadataParse(req.body));
return res.status(201).json({ data: connection });
}
} catch (error: any) {
const { message, statusCode = 500 } = error;
return res.status(statusCode).json({ error: { message } });
// Create OIDC connection
else {
const connection = await connectionAPIController.createOIDCConnection(oidcMetadataParse(req.body));
res.status(201).json({ data: connection });
}
};
@ -93,27 +79,19 @@ const handlePATCH = async (req: NextApiRequest, res: NextApiResponse) => {
const { isSAML, isOIDC } = strategyChecker(req);
if (!isSAML && !isOIDC) {
return res.status(400).json({ error: { message: 'Missing SSO connection params' } });
throw new ApiError('Missing SSO connection params', 400);
}
try {
// Update SAML connection
if (isSAML) {
await connectionAPIController.updateSAMLConnection(req.body);
// Update SAML connection
if (isSAML) {
await connectionAPIController.updateSAMLConnection(req.body);
res.status(204).end();
}
return res.status(204).end();
}
// Update OIDC connection
if (isOIDC) {
await connectionAPIController.updateOIDCConnection(oidcMetadataParse(req.body) as any);
return res.status(204).end();
}
} catch (error: any) {
const { message, statusCode = 500 } = error;
return res.status(statusCode).json({ error: { message } });
// Update OIDC connection
else {
await connectionAPIController.updateOIDCConnection(oidcMetadataParse(req.body));
res.status(204).end();
}
};
@ -126,15 +104,9 @@ const handleDELETE = async (req: NextApiRequest, res: NextApiResponse) => {
clientSecret: string;
};
try {
await connectionAPIController.deleteConnections({ clientID, clientSecret });
await connectionAPIController.deleteConnections({ clientID, clientSecret });
return res.status(200).json({ data: null });
} catch (error: any) {
const { message, statusCode = 500 } = error;
return res.status(statusCode).json({ error: { message } });
}
res.json({ data: null });
};
export default handler;

View File

@ -1,21 +1,12 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import jackson from '@lib/jackson';
import { defaultHandler } from '@lib/api';
import { ApiError } from '@lib/error';
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const { method } = req;
try {
switch (method) {
case 'GET':
return await handleGET(req, res);
default:
res.setHeader('Allow', 'GET');
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,
});
};
const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
@ -26,18 +17,14 @@ const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
const { data: directory, error } = await directorySyncController.directories.get(directoryId);
if (error) {
return res.status(error.code).json({ error });
}
if (!directory) {
return res.status(404).json({ error: { message: 'Directory not found.' } });
throw new ApiError(error.message, error.code);
}
const event = await directorySyncController.webhookLogs
.setTenantAndProduct(directory.tenant, directory.product)
.get(eventId);
return res.status(200).json({ data: event });
res.json({ data: event });
};
export default handler;

View File

@ -1,23 +1,13 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import jackson from '@lib/jackson';
import { defaultHandler } from '@lib/api';
import { ApiError } from '@lib/error';
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const { method } = req;
try {
switch (method) {
case 'GET':
return await handleGET(req, res);
case 'DELETE':
return await handleDELETE(req, res);
default:
res.setHeader('Allow', 'GET, 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,
DELETE: handleDELETE,
});
};
// Get all events
@ -29,11 +19,7 @@ const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
const { data: directory, error } = await directorySyncController.directories.get(directoryId);
if (error) {
return res.status(error.code).json({ error });
}
if (!directory) {
return res.status(404).json({ error: { message: 'Directory not found.' } });
throw new ApiError(error.message, error.code);
}
const pageOffset = parseInt(offset);
@ -47,7 +33,7 @@ const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
directoryId,
});
return res.status(200).json({ data: events });
res.json({ data: events });
};
// Delete all events
@ -59,18 +45,14 @@ const handleDELETE = async (req: NextApiRequest, res: NextApiResponse) => {
const { data: directory, error } = await directorySyncController.directories.get(directoryId);
if (error) {
return res.status(error.code).json({ error });
}
if (!directory) {
return res.status(404).json({ error: { message: 'Directory not found.' } });
throw new ApiError(error.message, error.code);
}
await directorySyncController.webhookLogs
.setTenantAndProduct(directory.tenant, directory.product)
.deleteAll(directoryId);
return res.status(200).json({ data: null });
res.json({ data: null });
};
export default handler;

View File

@ -1,16 +1,12 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import jackson from '@lib/jackson';
import { defaultHandler } from '@lib/api';
import { ApiError } from '@lib/error';
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const { method } = req;
switch (method) {
case 'GET':
return await handleGET(req, res);
default:
res.setHeader('Allow', 'GET');
res.status(405).json({ error: { message: `Method ${method} Not Allowed` } });
}
await defaultHandler(req, res, {
GET: handleGET,
});
};
// Get the details of a group
@ -19,23 +15,21 @@ const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
const { directoryId, groupId } = req.query as { directoryId: string; groupId: string };
const { data: directory } = await directorySyncController.directories.get(directoryId);
const { data: directory, error } = await directorySyncController.directories.get(directoryId);
if (!directory) {
return res.status(404).json({ error: { message: 'Directory not found.' } });
if (error) {
throw new ApiError(error.message, error.code);
}
const { data: group, error } = await directorySyncController.groups
const { data: group, error: groupError } = await directorySyncController.groups
.setTenantAndProduct(directory.tenant, directory.product)
.get(groupId);
if (error) {
return res.status(400).json({ error });
if (groupError) {
throw new ApiError(groupError.message, groupError.code);
}
if (group) {
return res.status(200).json({ data: group });
}
res.json({ data: group });
};
export default handler;

View File

@ -1,16 +1,12 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import jackson from '@lib/jackson';
import { defaultHandler } from '@lib/api';
import { ApiError } from '@lib/error';
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const { method } = req;
switch (method) {
case 'GET':
return await handleGET(req, res);
default:
res.setHeader('Allow', 'GET');
res.status(405).json({ error: { message: `Method ${method} Not Allowed` } });
}
await defaultHandler(req, res, {
GET: handleGET,
});
};
// Get all groups for a directory
@ -19,26 +15,24 @@ const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
const { directoryId, offset, limit } = req.query as { directoryId: string; offset: string; limit: string };
const { data: directory } = await directorySyncController.directories.get(directoryId);
const { data: directory, error } = await directorySyncController.directories.get(directoryId);
if (!directory) {
return res.status(404).json({ error: { message: 'Directory not found.' } });
if (error) {
throw new ApiError(error.message, error.code);
}
const pageOffset = parseInt(offset);
const pageLimit = parseInt(limit);
const { data: groups, error } = await directorySyncController.groups
const { data: groups, error: groupsError } = await directorySyncController.groups
.setTenantAndProduct(directory.tenant, directory.product)
.getAll({ pageOffset, pageLimit, directoryId });
if (error) {
return res.status(error.code).json({ error });
if (groupsError) {
throw new ApiError(groupsError.message, groupsError.code);
}
if (groups) {
return res.status(200).json({ data: groups });
}
res.json({ data: groups });
};
export default handler;

View File

@ -1,20 +1,14 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import jackson from '@lib/jackson';
import { defaultHandler } from '@lib/api';
import { ApiError } from '@lib/error';
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const { method } = req;
switch (method) {
case 'PATCH':
return await handlePATCH(req, res);
case 'GET':
return await handleGET(req, res);
case 'DELETE':
return await handleDELETE(req, res);
default:
res.setHeader('Allow', 'GET, PATCH, DELETE');
res.status(405).json({ error: { message: `Method ${method} Not Allowed` } });
}
await defaultHandler(req, res, {
GET: handleGET,
PATCH: handlePATCH,
DELETE: handleDELETE,
});
};
// Update a directory configuration
@ -25,13 +19,11 @@ const handlePATCH = async (req: NextApiRequest, res: NextApiResponse) => {
const { data, error } = await directorySyncController.directories.update(directoryId, req.body);
if (data) {
return res.status(200).json({ data });
if (error) {
throw new ApiError(error.message, error.code);
}
if (error) {
return res.status(error.code).json({ error });
}
res.json({ data });
};
// Get a directory configuration
@ -42,13 +34,11 @@ const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
const { data, error } = await directorySyncController.directories.get(directoryId);
if (data) {
return res.status(200).json({ data });
if (error) {
throw new ApiError(error.message, error.code);
}
if (error) {
return res.status(error.code).json({ error });
}
res.json({ data });
};
// Delete a directory configuration
@ -60,10 +50,10 @@ const handleDELETE = async (req: NextApiRequest, res: NextApiResponse) => {
const { error } = await directorySyncController.directories.delete(directoryId);
if (error) {
return res.status(error.code).json({ error });
throw new ApiError(error.message, error.code);
}
return res.json({ data: null });
res.json({ data: null });
};
export default handler;

View File

@ -1,16 +1,12 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import jackson from '@lib/jackson';
import { defaultHandler } from '@lib/api';
import { ApiError } from '@lib/error';
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const { method } = req;
switch (method) {
case 'GET':
return await handleGET(req, res);
default:
res.setHeader('Allow', 'GET');
res.status(405).json({ error: { message: `Method ${method} Not Allowed` } });
}
await defaultHandler(req, res, {
GET: handleGET,
});
};
// Get the details of a user
@ -19,23 +15,21 @@ const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
const { directoryId, userId } = req.query as { directoryId: string; userId: string };
const { data: directory } = await directorySyncController.directories.get(directoryId);
const { data: directory, error } = await directorySyncController.directories.get(directoryId);
if (!directory) {
return res.status(404).json({ error: { message: 'Directory not found.' } });
if (error) {
throw new ApiError(error.message, error.code);
}
const { data: user, error } = await directorySyncController.users
const { data: user, error: userError } = await directorySyncController.users
.setTenantAndProduct(directory.tenant, directory.product)
.get(userId);
if (error) {
return res.status(400).json({ error });
if (userError) {
throw new ApiError(userError.message, userError.code);
}
if (user) {
return res.status(200).json({ data: user });
}
res.json({ data: user });
};
export default handler;

View File

@ -1,16 +1,12 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import jackson from '@lib/jackson';
import { defaultHandler } from '@lib/api';
import { ApiError } from '@lib/error';
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const { method } = req;
switch (method) {
case 'GET':
return await handleGET(req, res);
default:
res.setHeader('Allow', 'GET');
res.status(405).json({ error: { message: `Method ${method} Not Allowed` } });
}
await defaultHandler(req, res, {
GET: handleGET,
});
};
// Get all users in a directory
@ -19,26 +15,24 @@ const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
const { directoryId, offset, limit } = req.query as { directoryId: string; offset: string; limit: string };
const { data: directory } = await directorySyncController.directories.get(directoryId);
const { data: directory, error } = await directorySyncController.directories.get(directoryId);
if (!directory) {
return res.status(404).json({ error: { message: 'Directory not found.' } });
if (error) {
throw new ApiError(error.message, error.code);
}
const pageOffset = parseInt(offset);
const pageLimit = parseInt(limit);
const { data: users, error } = await directorySyncController.users
const { data: users, error: usersError } = await directorySyncController.users
.setTenantAndProduct(directory.tenant, directory.product)
.getAll({ pageOffset, pageLimit, directoryId });
if (error) {
return res.status(error.code).json({ error });
if (usersError) {
throw new ApiError(usersError.message, usersError.code);
}
if (users) {
return res.status(200).json({ data: users });
}
res.json({ data: users });
};
export default handler;

View File

@ -1,20 +1,15 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import type { DirectoryType } from '@boxyhq/saml-jackson';
import jackson from '@lib/jackson';
import { defaultHandler } from '@lib/api';
import { ApiError } from '@lib/error';
import { parsePaginateApiParams } from '@lib/utils';
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const { method } = req;
switch (method) {
case 'POST':
return await handlePOST(req, res);
case 'GET':
return await handleGET(req, res);
default:
res.setHeader('Allow', 'POST, GET');
res.status(405).json({ error: { message: `Method ${method} Not Allowed` } });
}
await defaultHandler(req, res, {
GET: handleGET,
POST: handlePOST,
});
};
const handlePOST = async (req: NextApiRequest, res: NextApiResponse) => {
@ -32,13 +27,11 @@ const handlePOST = async (req: NextApiRequest, res: NextApiResponse) => {
google_domain,
});
if (data) {
return res.status(201).json({ data });
if (error) {
throw new ApiError(error.message, error.code);
}
if (error) {
return res.status(error.code).json({ error });
}
res.status(201).json({ data });
};
const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
@ -60,13 +53,11 @@ const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
res.setHeader('jackson-pagetoken', nextPageToken);
}
if (data) {
return res.status(200).json({ data });
if (error) {
throw new ApiError(error.message, error.code);
}
if (error) {
return res.status(error.code).json({ error });
}
res.json({ data });
};
export default handler;

View File

@ -1,16 +1,11 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import jackson from '@lib/jackson';
import { defaultHandler } from '@lib/api';
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const { method } = req;
switch (method) {
case 'GET':
return await handleGET(req, res);
default:
res.setHeader('Allow', 'GET');
res.status(405).json({ error: { message: `Method ${method} Not Allowed` } });
}
await defaultHandler(req, res, {
GET: handleGET,
});
};
// Get the directory providers
@ -19,7 +14,7 @@ const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
const providers = directorySyncController.providers();
return res.json({ data: providers });
res.json({ data: providers });
};
export default handler;

View File

@ -3,20 +3,12 @@ import axios from 'axios';
import { getToken } from '@lib/retraced';
import { retracedOptions } from '@lib/env';
import { defaultHandler } from '@lib/api';
async function handler(req: NextApiRequest, res: NextApiResponse) {
const { method } = req;
switch (method) {
case 'GET':
return await getGroups(req, res);
default:
res.setHeader('Allow', 'GET');
res.status(405).json({
data: null,
error: { message: `Method ${method} Not Allowed` },
});
}
await defaultHandler(req, res, {
GET: getGroups,
});
}
const getGroups = async (req: NextApiRequest, res: NextApiResponse) => {
@ -39,7 +31,7 @@ const getGroups = async (req: NextApiRequest, res: NextApiResponse) => {
}
);
return res.status(200).json({
res.json({
data,
error: null,
});

View File

@ -4,20 +4,12 @@ import axios from 'axios';
import type { Project } from 'types/retraced';
import { getToken } from '@lib/retraced';
import { retracedOptions } from '@lib/env';
import { defaultHandler } from '@lib/api';
async function handler(req: NextApiRequest, res: NextApiResponse) {
const { method } = req;
switch (method) {
case 'GET':
return await getProject(req, res);
default:
res.setHeader('Allow', 'GET');
res.status(405).json({
data: null,
error: { message: `Method ${method} Not Allowed` },
});
}
await defaultHandler(req, res, {
GET: getProject,
});
}
const getProject = async (req: NextApiRequest, res: NextApiResponse) => {
@ -34,7 +26,7 @@ const getProject = async (req: NextApiRequest, res: NextApiResponse) => {
}
);
return res.status(201).json({
res.status(201).json({
data,
error: null,
});

View File

@ -2,20 +2,12 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import * as Retraced from '@retracedhq/retraced';
import { retracedOptions } from '@lib/env';
import { defaultHandler } from '@lib/api';
async function handler(req: NextApiRequest, res: NextApiResponse) {
const { method } = req;
switch (method) {
case 'GET':
return await getViewerToken(req, res);
default:
res.setHeader('Allow', 'GET');
res.status(405).json({
data: null,
error: { message: `Method ${method} Not Allowed` },
});
}
await defaultHandler(req, res, {
GET: getViewerToken,
});
}
// Get A viewer token and send it to the client, the client will use this token to initialize the logs-viewer
@ -32,7 +24,7 @@ const getViewerToken = async (req: NextApiRequest, res: NextApiResponse) => {
const viewerToken = await retraced.getViewerToken(groupId as string, 'Admin-Portal', true);
return res.status(200).json({
res.json({
data: {
viewerToken,
},

View File

@ -4,21 +4,13 @@ import axios from 'axios';
import type { Project } from 'types/retraced';
import { getToken } from '@lib/retraced';
import { retracedOptions } from '@lib/env';
import { defaultHandler } from '@lib/api';
async function handler(req: NextApiRequest, res: NextApiResponse) {
const { method } = req;
switch (method) {
case 'GET':
return await getProjects(req, res);
case 'POST':
return await createProject(req, res);
default:
res.setHeader('Allow', 'GET, POST');
res.status(405).json({
error: { message: `Method ${method} Not Allowed` },
});
}
await defaultHandler(req, res, {
GET: getProjects,
POST: createProject,
});
}
const createProject = async (req: NextApiRequest, res: NextApiResponse) => {
@ -38,39 +30,31 @@ const createProject = async (req: NextApiRequest, res: NextApiResponse) => {
}
);
return res.status(201).json({
res.status(201).json({
data,
});
};
const getProjects = async (req: NextApiRequest, res: NextApiResponse) => {
try {
const token = await getToken(req);
const token = await getToken(req);
const { offset, limit } = req.query as {
offset: string;
limit: string;
};
const { offset, limit } = req.query as {
offset: string;
limit: string;
};
const { data } = await axios.get<{ projects: Project[] }>(
`${retracedOptions?.hostUrl}/admin/v1/projects?offset=${+(offset || 0)}&limit=${+(limit || 0)}`,
{
headers: {
Authorization: `id=${token.id} token=${token.token} admin_token=${retracedOptions.adminToken}`,
},
}
);
return res.status(200).json({
data,
});
} catch (ex: any) {
return res.status(500).json({
error: {
message: ex?.message || ex?.response?.message || ex,
const { data } = await axios.get<{ projects: Project[] }>(
`${retracedOptions?.hostUrl}/admin/v1/projects?offset=${+(offset || 0)}&limit=${+(limit || 0)}`,
{
headers: {
Authorization: `id=${token.id} token=${token.token} admin_token=${retracedOptions.adminToken}`,
},
});
}
}
);
res.json({
data,
});
};
export default handler;

View File

@ -1,27 +1,15 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import jackson from '@lib/jackson';
import { defaultHandler } from '@lib/api';
import { ApiError } from '@lib/error';
import { parsePaginateApiParams } from '@lib/utils';
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const { method } = req;
try {
switch (method) {
case 'POST':
return await handlePOST(req, res);
case 'GET':
return await handleGET(req, res);
case 'DELETE':
return await handleDELETE(req, res);
default:
res.setHeader('Allow', 'POST, GET, 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,
DELETE: handleDELETE,
});
};
// Create a new setup link
@ -30,7 +18,7 @@ const handlePOST = async (req: NextApiRequest, res: NextApiResponse) => {
const setupLink = await setupLinkController.create(req.body);
return res.status(201).json({ data: setupLink });
res.status(201).json({ data: setupLink });
};
const handleDELETE = async (req: NextApiRequest, res: NextApiResponse) => {
@ -40,7 +28,7 @@ const handleDELETE = async (req: NextApiRequest, res: NextApiResponse) => {
await setupLinkController.remove({ id });
return res.json({ data: {} });
res.json({ data: {} });
};
const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
@ -54,23 +42,18 @@ const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
const { pageOffset, pageLimit, pageToken } = parsePaginateApiParams(req.query);
if (!token && !service) {
return res.status(404).json({
error: {
message: 'Setup link is invalid',
code: 404,
},
});
throw new ApiError('Either token or service is required', 400);
}
// Get a setup link by token
if (token) {
const setupLink = await setupLinkController.getByToken(token);
return res.json({ data: setupLink });
res.json({ data: setupLink });
}
// Get a setup link by service
if (service) {
else if (service) {
const setupLinksPaginated = await setupLinkController.filterBy({
service: service as any,
pageOffset,
@ -82,7 +65,7 @@ const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
res.setHeader('jackson-pagetoken', setupLinksPaginated.pageToken);
}
return res.json({ data: setupLinksPaginated.data });
res.json({ data: setupLinksPaginated.data });
}
};

View File

@ -1,37 +1,22 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import jackson from '@lib/jackson';
import type { IAdminController } from '@boxyhq/saml-jackson';
import { defaultHandler } from '@lib/api';
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const { method } = req;
try {
const { adminController } = await jackson();
switch (method) {
case 'GET':
return await handleGET(req, res, adminController);
default:
res.setHeader('Allow', 'GET');
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,
});
};
// Get SAML Trace by traceId
const handleGET = async (req: NextApiRequest, res: NextApiResponse, adminController: IAdminController) => {
const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
const { adminController } = await jackson();
const { traceId } = req.query as { traceId: string };
const trace = await adminController.getSSOTraceById(traceId);
if (!trace) {
return res.status(404).json({ error: { message: 'Trace not found.' } });
}
return res.json({ data: trace });
res.json({ data: trace });
};
export default handler;

View File

@ -1,31 +1,19 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import jackson from '@lib/jackson';
import type { IAdminController } from '@boxyhq/saml-jackson';
import { defaultHandler } from '@lib/api';
import { parsePaginateApiParams } from '@lib/utils';
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const { method } = req;
try {
const { adminController } = await jackson();
switch (method) {
case 'GET':
return await handleGET(req, res, adminController);
default:
res.setHeader('Allow', 'GET');
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,
});
};
// Get SAML Traces
const handleGET = async (req: NextApiRequest, res: NextApiResponse, adminController: IAdminController) => {
const params = req.query;
const { pageOffset, pageLimit, pageToken } = parsePaginateApiParams(params);
const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
const { adminController } = await jackson();
const { pageOffset, pageLimit, pageToken } = parsePaginateApiParams(req.query);
const tracesPaginated = await adminController.getAllSSOTraces(pageOffset, pageLimit, pageToken);
@ -33,7 +21,7 @@ const handleGET = async (req: NextApiRequest, res: NextApiResponse, adminControl
res.setHeader('jackson-pagetoken', tracesPaginated.pageToken);
}
return res.json({ data: tracesPaginated.data });
res.json({ data: tracesPaginated.data });
};
export default handler;

View File

@ -1,22 +1,13 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import axios from 'axios';
import { terminusOptions } from '@lib/env';
import { defaultHandler } from '@lib/api';
async function handler(req: NextApiRequest, res: NextApiResponse) {
const { method } = req;
switch (method) {
case 'GET':
return await getModel(req, res);
case 'POST':
return await saveModel(req, res);
default:
res.setHeader('Allow', 'GET');
res.status(405).json({
data: null,
error: { message: `Method ${method} Not Allowed` },
});
}
await defaultHandler(req, res, {
GET: getModel,
POST: saveModel,
});
}
const getTerminusUrl = (id) => {
@ -33,7 +24,7 @@ const getModel = async (req: NextApiRequest, res: NextApiResponse) => {
},
});
return res.status(201).json({
res.json({
data,
error: null,
});
@ -49,7 +40,7 @@ const saveModel = async (req: NextApiRequest, res: NextApiResponse) => {
},
});
return res.status(201).json({
res.status(201).json({
data,
error: null,
});

View File

@ -1,20 +1,12 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import axios from 'axios';
import { terminusOptions } from '@lib/env';
import { defaultHandler } from '@lib/api';
async function handler(req: NextApiRequest, res: NextApiResponse) {
const { method } = req;
switch (method) {
case 'GET':
return await getRoles(req, res);
default:
res.setHeader('Allow', 'GET');
res.status(405).json({
data: null,
error: { message: `Method ${method} Not Allowed` },
});
}
await defaultHandler(req, res, {
GET: getRoles,
});
}
const getRoles = async (req: NextApiRequest, res: NextApiResponse) => {
@ -25,7 +17,7 @@ const getRoles = async (req: NextApiRequest, res: NextApiResponse) => {
},
});
return res.status(201).json({
res.json({
data,
error: null,
});