Files
pangolin/server/private/routers/external.ts

463 lines
13 KiB
TypeScript

/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import * as certificates from "#private/routers/certificates";
import { createStore } from "#private/lib/rateLimitStore";
import * as billing from "#private/routers/billing";
import * as remoteExitNode from "#private/routers/remoteExitNode";
import * as loginPage from "#private/routers/loginPage";
import * as orgIdp from "#private/routers/orgIdp";
import * as domain from "#private/routers/domain";
import * as auth from "#private/routers/auth";
import * as license from "#private/routers/license";
import * as generateLicense from "./generatedLicense";
import * as logs from "#private/routers/auditLogs";
import * as misc from "#private/routers/misc";
import * as reKey from "#private/routers/re-key";
import {
verifyOrgAccess,
verifyUserHasAction,
verifyUserIsServerAdmin,
verifySiteAccess,
verifyClientAccess
} from "@server/middlewares";
import { ActionsEnum } from "@server/auth/actions";
import {
logActionAudit,
verifyCertificateAccess,
verifyIdpAccess,
verifyLoginPageAccess,
verifyRemoteExitNodeAccess,
verifyValidSubscription
} from "#private/middlewares";
import rateLimit, { ipKeyGenerator } from "express-rate-limit";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { verifyValidLicense } from "../middlewares/verifyValidLicense";
import { build } from "@server/build";
import {
unauthenticated as ua,
authenticated as a,
authRouter as aa
} from "@server/routers/external";
export const authenticated = a;
export const unauthenticated = ua;
export const authRouter = aa;
unauthenticated.post(
"/remote-exit-node/quick-start",
verifyValidLicense,
rateLimit({
windowMs: 60 * 60 * 1000,
max: 5,
keyGenerator: (req) => `${req.path}:${ipKeyGenerator(req.ip || "")}`,
handler: (req, res, next) => {
const message = `You can only create 5 remote exit nodes every hour. Please try again later.`;
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
},
store: createStore()
}),
remoteExitNode.quickStartRemoteExitNode
);
authenticated.put(
"/org/:orgId/idp/oidc",
verifyValidLicense,
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.createIdp),
logActionAudit(ActionsEnum.createIdp),
orgIdp.createOrgOidcIdp
);
authenticated.post(
"/org/:orgId/idp/:idpId/oidc",
verifyValidLicense,
verifyOrgAccess,
verifyIdpAccess,
verifyUserHasAction(ActionsEnum.updateIdp),
logActionAudit(ActionsEnum.updateIdp),
orgIdp.updateOrgOidcIdp
);
authenticated.delete(
"/org/:orgId/idp/:idpId",
verifyValidLicense,
verifyOrgAccess,
verifyIdpAccess,
verifyUserHasAction(ActionsEnum.deleteIdp),
logActionAudit(ActionsEnum.deleteIdp),
orgIdp.deleteOrgIdp
);
authenticated.get(
"/org/:orgId/idp/:idpId",
verifyValidLicense,
verifyOrgAccess,
verifyIdpAccess,
verifyUserHasAction(ActionsEnum.getIdp),
orgIdp.getOrgIdp
);
authenticated.get(
"/org/:orgId/idp",
verifyValidLicense,
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.listIdps),
orgIdp.listOrgIdps
);
authenticated.get("/org/:orgId/idp", orgIdp.listOrgIdps); // anyone can see this; it's just a list of idp names and ids
authenticated.get(
"/org/:orgId/certificate/:domainId/:domain",
verifyValidLicense,
verifyOrgAccess,
verifyCertificateAccess,
verifyUserHasAction(ActionsEnum.getCertificate),
certificates.getCertificate
);
authenticated.post(
"/org/:orgId/certificate/:certId/restart",
verifyValidLicense,
verifyOrgAccess,
verifyCertificateAccess,
verifyUserHasAction(ActionsEnum.restartCertificate),
logActionAudit(ActionsEnum.restartCertificate),
certificates.restartCertificate
);
if (build === "saas") {
unauthenticated.post(
"/quick-start",
rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
keyGenerator: (req) => req.path,
handler: (req, res, next) => {
const message = `We're too busy right now. Please try again later.`;
return next(
createHttpError(HttpCode.TOO_MANY_REQUESTS, message)
);
},
store: createStore()
}),
auth.quickStart
);
authenticated.post(
"/org/:orgId/billing/create-checkout-session",
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.billing),
logActionAudit(ActionsEnum.billing),
billing.createCheckoutSession
);
authenticated.post(
"/org/:orgId/billing/create-portal-session",
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.billing),
logActionAudit(ActionsEnum.billing),
billing.createPortalSession
);
authenticated.get(
"/org/:orgId/billing/subscription",
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.billing),
billing.getOrgSubscription
);
authenticated.get(
"/org/:orgId/billing/usage",
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.billing),
billing.getOrgUsage
);
authenticated.get(
"/org/:orgId/license",
verifyOrgAccess,
generateLicense.listSaasLicenseKeys
);
authenticated.put(
"/org/:orgId/license",
verifyOrgAccess,
generateLicense.generateNewLicense
);
authenticated.post(
"/send-support-request",
rateLimit({
windowMs: 15 * 60 * 1000,
max: 3,
keyGenerator: (req) =>
`sendSupportRequest:${req.user?.userId || ipKeyGenerator(req.ip || "")}`,
handler: (req, res, next) => {
const message = `You can only send 3 support requests every 15 minutes. Please try again later.`;
return next(
createHttpError(HttpCode.TOO_MANY_REQUESTS, message)
);
},
store: createStore()
}),
misc.sendSupportEmail
);
}
authenticated.get(
"/domain/namespaces",
verifyValidLicense,
domain.listDomainNamespaces
);
authenticated.get(
"/domain/check-namespace-availability",
verifyValidLicense,
domain.checkDomainNamespaceAvailability
);
authenticated.put(
"/org/:orgId/remote-exit-node",
verifyValidLicense,
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.createRemoteExitNode),
logActionAudit(ActionsEnum.createRemoteExitNode),
remoteExitNode.createRemoteExitNode
);
authenticated.get(
"/org/:orgId/remote-exit-nodes",
verifyValidLicense,
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.listRemoteExitNode),
remoteExitNode.listRemoteExitNodes
);
authenticated.get(
"/org/:orgId/remote-exit-node/:remoteExitNodeId",
verifyValidLicense,
verifyOrgAccess,
verifyRemoteExitNodeAccess,
verifyUserHasAction(ActionsEnum.getRemoteExitNode),
remoteExitNode.getRemoteExitNode
);
authenticated.get(
"/org/:orgId/pick-remote-exit-node-defaults",
verifyValidLicense,
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.createRemoteExitNode),
remoteExitNode.pickRemoteExitNodeDefaults
);
authenticated.delete(
"/org/:orgId/remote-exit-node/:remoteExitNodeId",
verifyValidLicense,
verifyOrgAccess,
verifyRemoteExitNodeAccess,
verifyUserHasAction(ActionsEnum.deleteRemoteExitNode),
logActionAudit(ActionsEnum.deleteRemoteExitNode),
remoteExitNode.deleteRemoteExitNode
);
authenticated.put(
"/org/:orgId/login-page",
verifyValidLicense,
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.createLoginPage),
logActionAudit(ActionsEnum.createLoginPage),
loginPage.createLoginPage
);
authenticated.post(
"/org/:orgId/login-page/:loginPageId",
verifyValidLicense,
verifyOrgAccess,
verifyLoginPageAccess,
verifyUserHasAction(ActionsEnum.updateLoginPage),
logActionAudit(ActionsEnum.updateLoginPage),
loginPage.updateLoginPage
);
authenticated.delete(
"/org/:orgId/login-page/:loginPageId",
verifyValidLicense,
verifyOrgAccess,
verifyLoginPageAccess,
verifyUserHasAction(ActionsEnum.deleteLoginPage),
logActionAudit(ActionsEnum.deleteLoginPage),
loginPage.deleteLoginPage
);
authenticated.get(
"/org/:orgId/login-page",
verifyValidLicense,
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.getLoginPage),
loginPage.getLoginPage
);
authenticated.get(
"/org/:orgId/login-page-branding",
verifyValidLicense,
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.getLoginPage),
logActionAudit(ActionsEnum.getLoginPage),
loginPage.getLoginPageBranding
);
authenticated.put(
"/org/:orgId/login-page-branding",
verifyValidLicense,
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.updateLoginPage),
logActionAudit(ActionsEnum.updateLoginPage),
loginPage.upsertLoginPageBranding
);
authenticated.delete(
"/org/:orgId/login-page-branding",
verifyValidLicense,
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.deleteLoginPage),
logActionAudit(ActionsEnum.deleteLoginPage),
loginPage.deleteLoginPageBranding
);
authRouter.post(
"/remoteExitNode/get-token",
verifyValidLicense,
rateLimit({
windowMs: 15 * 60 * 1000,
max: 900,
keyGenerator: (req) =>
`remoteExitNodeGetToken:${req.body.newtId || ipKeyGenerator(req.ip || "")}`,
handler: (req, res, next) => {
const message = `You can only request an remoteExitNodeToken token ${900} times every ${15} minutes. Please try again later.`;
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
},
store: createStore()
}),
remoteExitNode.getRemoteExitNodeToken
);
authRouter.post(
"/transfer-session-token",
verifyValidLicense,
rateLimit({
windowMs: 1 * 60 * 1000,
max: 60,
keyGenerator: (req) =>
`transferSessionToken:${ipKeyGenerator(req.ip || "")}`,
handler: (req, res, next) => {
const message = `You can only transfer a session token ${5} times every ${1} minute. Please try again later.`;
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
},
store: createStore()
}),
auth.transferSession
);
authenticated.post(
"/license/activate",
verifyUserIsServerAdmin,
license.activateLicense
);
authenticated.get(
"/license/keys",
verifyUserIsServerAdmin,
license.listLicenseKeys
);
authenticated.delete(
"/license/:licenseKey",
verifyUserIsServerAdmin,
license.deleteLicenseKey
);
authenticated.post(
"/license/recheck",
verifyUserIsServerAdmin,
license.recheckStatus
);
authenticated.get(
"/org/:orgId/logs/action",
verifyValidLicense,
verifyValidSubscription,
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.exportLogs),
logs.queryActionAuditLogs
);
authenticated.get(
"/org/:orgId/logs/action/export",
verifyValidLicense,
verifyValidSubscription,
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.exportLogs),
logActionAudit(ActionsEnum.exportLogs),
logs.exportActionAuditLogs
);
authenticated.get(
"/org/:orgId/logs/access",
verifyValidLicense,
verifyValidSubscription,
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.exportLogs),
logs.queryAccessAuditLogs
);
authenticated.get(
"/org/:orgId/logs/access/export",
verifyValidLicense,
verifyValidSubscription,
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.exportLogs),
logActionAudit(ActionsEnum.exportLogs),
logs.exportAccessAuditLogs
);
authenticated.post(
"/re-key/:clientId/regenerate-client-secret",
verifyClientAccess, // this is first to set the org id
verifyValidLicense,
verifyValidSubscription,
verifyUserHasAction(ActionsEnum.reGenerateSecret),
reKey.reGenerateClientSecret
);
authenticated.post(
"/re-key/:siteId/regenerate-site-secret",
verifySiteAccess, // this is first to set the org id
verifyValidLicense,
verifyValidSubscription,
verifyUserHasAction(ActionsEnum.reGenerateSecret),
reKey.reGenerateSiteSecret
);
authenticated.put(
"/re-key/:orgId/regenerate-remote-exit-node-secret",
verifyValidLicense,
verifyValidSubscription,
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.reGenerateSecret),
reKey.reGenerateExitNodeSecret
);