This commit is contained in:
Fred KISSIE
2025-11-11 23:35:20 +01:00
parent 08e43400e4
commit f58cf68f7c
15 changed files with 427 additions and 100 deletions

View File

@@ -150,7 +150,6 @@
"resourceAdd": "Add Resource", "resourceAdd": "Add Resource",
"resourceErrorDelte": "Error deleting resource", "resourceErrorDelte": "Error deleting resource",
"authentication": "Authentication", "authentication": "Authentication",
"authPages": "Auth Page",
"protected": "Protected", "protected": "Protected",
"notProtected": "Not Protected", "notProtected": "Not Protected",
"resourceMessageRemove": "Once removed, the resource will no longer be accessible. All targets associated with the resource will also be removed.", "resourceMessageRemove": "Once removed, the resource will no longer be accessible. All targets associated with the resource will also be removed.",

View File

@@ -123,7 +123,9 @@ export enum ActionsEnum {
getBlueprint = "getBlueprint", getBlueprint = "getBlueprint",
applyBlueprint = "applyBlueprint", applyBlueprint = "applyBlueprint",
viewLogs = "viewLogs", viewLogs = "viewLogs",
exportLogs = "exportLogs" exportLogs = "exportLogs",
updateOrgAuthPage = "updateOrgAuthPage",
getOrgAuthPage = "getOrgAuthPage"
} }
export async function checkUserActionPermission( export async function checkUserActionPermission(

View File

@@ -0,0 +1,13 @@
import z, { type ZodSchema } from "zod";
export function createResponseBodySchema<T extends ZodSchema>(dataSchema: T) {
return z.object({
data: dataSchema.nullable(),
success: z.boolean(),
error: z.boolean(),
message: z.string(),
status: z.number()
});
}
export default createResponseBodySchema;

View File

@@ -0,0 +1,107 @@
import { eq } from "drizzle-orm";
/*
* 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 { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { db, orgAuthPages } from "@server/db";
import { orgs } from "@server/db";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
import createResponseBodySchema from "@server/lib/createResponseBodySchema";
const getOrgAuthPageParamsSchema = z
.object({
orgId: z.string()
})
.strict();
const reponseSchema = createResponseBodySchema(
z
.object({
logoUrl: z.string().url(),
logoWidth: z.number().min(1),
logoHeight: z.number().min(1),
title: z.string(),
subtitle: z.string().optional(),
resourceTitle: z.string(),
resourceSubtitle: z.string().optional()
})
.strict()
);
export type GetOrgAuthPageResponse = z.infer<typeof reponseSchema>;
registry.registerPath({
method: "get",
path: "/org/{orgId}/auth-page",
description: "Get an organization auth page",
tags: [OpenAPITags.Org],
request: {
params: getOrgAuthPageParamsSchema
},
responses: {
200: {
description: "",
content: {
"application/json": {
schema: reponseSchema
}
}
}
}
});
export async function getOrgAuthPage(
req: Request,
res: Response,
next: NextFunction
): Promise<any> {
try {
const parsedParams = getOrgAuthPageParamsSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedParams.error).toString()
)
);
}
const { orgId } = parsedParams.data;
const [orgAuthPage] = await db
.select()
.from(orgAuthPages)
.leftJoin(orgs, eq(orgs.orgId, orgAuthPages.orgId))
.where(eq(orgs.orgId, orgId))
.limit(1);
return response(res, {
data: orgAuthPage?.orgAuthPages ?? null,
success: true,
error: false,
message: "Organization auth page retrieved successfully",
status: HttpCode.OK
});
} catch (error) {
logger.error(error);
return next(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}

View File

@@ -0,0 +1,15 @@
/*
* 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.
*/
export * from "./updateOrgAuthPage";
export * from "./getOrgAuthPage";

View File

@@ -0,0 +1,141 @@
/*
* 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 { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { db, orgAuthPages } from "@server/db";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
import createResponseBodySchema from "@server/lib/createResponseBodySchema";
const updateOrgAuthPageParamsSchema = z
.object({
orgId: z.string()
})
.strict();
const updateOrgAuthPageBodySchema = z
.object({
logoUrl: z.string().url(),
logoWidth: z.number().min(1),
logoHeight: z.number().min(1),
title: z.string(),
subtitle: z.string().optional(),
resourceTitle: z.string(),
resourceSubtitle: z.string().optional()
})
.strict();
const reponseSchema = createResponseBodySchema(updateOrgAuthPageBodySchema);
export type UpdateOrgAuthPageResponse = z.infer<typeof reponseSchema>;
registry.registerPath({
method: "put",
path: "/org/{orgId}/auth-page",
description: "Update an organization auth page",
tags: [OpenAPITags.Org],
request: {
params: updateOrgAuthPageParamsSchema,
body: {
content: {
"application/json": {
schema: updateOrgAuthPageBodySchema
}
}
}
},
responses: {
200: {
description: "",
content: {
"application/json": {
schema: reponseSchema
}
}
}
}
});
export async function updateOrgAuthPage(
req: Request,
res: Response,
next: NextFunction
): Promise<any> {
try {
const parsedParams = updateOrgAuthPageParamsSchema.safeParse(
req.params
);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedParams.error).toString()
)
);
}
const parsedBody = updateOrgAuthPageBodySchema.safeParse(req.body);
if (!parsedBody.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedBody.error).toString()
)
);
}
const { orgId } = parsedParams.data;
const body = parsedBody.data;
const updatedOrgAuthPages = await db
.insert(orgAuthPages)
.values({
...body,
orgId
})
.onConflictDoUpdate({
target: orgAuthPages.orgId,
set: {
...body
}
})
.returning();
if (updatedOrgAuthPages.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Organization with ID ${orgId} not found`
)
);
}
return response(res, {
data: updatedOrgAuthPages[0],
success: true,
error: false,
message: "Organization auth page updated successfully",
status: HttpCode.OK
});
} catch (error) {
logger.error(error);
return next(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}

View File

@@ -17,6 +17,7 @@ import * as billing from "#private/routers/billing";
import * as remoteExitNode from "#private/routers/remoteExitNode"; import * as remoteExitNode from "#private/routers/remoteExitNode";
import * as loginPage from "#private/routers/loginPage"; import * as loginPage from "#private/routers/loginPage";
import * as orgIdp from "#private/routers/orgIdp"; import * as orgIdp from "#private/routers/orgIdp";
import * as authPage from "#private/routers/authPage";
import * as domain from "#private/routers/domain"; import * as domain from "#private/routers/domain";
import * as auth from "#private/routers/auth"; import * as auth from "#private/routers/auth";
import * as license from "#private/routers/license"; import * as license from "#private/routers/license";
@@ -403,3 +404,23 @@ authenticated.get(
logActionAudit(ActionsEnum.exportLogs), logActionAudit(ActionsEnum.exportLogs),
logs.exportAccessAuditLogs logs.exportAccessAuditLogs
); );
authenticated.put(
"/org/:orgId/auth-page",
verifyValidLicense,
verifyValidSubscription,
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.updateOrgAuthPage),
logActionAudit(ActionsEnum.updateOrgAuthPage),
authPage.updateOrgAuthPage
);
authenticated.get(
"/org/:orgId/auth-page",
verifyValidLicense,
verifyValidSubscription,
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.getOrgAuthPage),
logActionAudit(ActionsEnum.getOrgAuthPage),
authPage.getOrgAuthPage
);

View File

@@ -80,7 +80,7 @@ authenticated.post(
verifyOrgAccess, verifyOrgAccess,
verifyUserHasAction(ActionsEnum.updateOrg), verifyUserHasAction(ActionsEnum.updateOrg),
logActionAudit(ActionsEnum.updateOrg), logActionAudit(ActionsEnum.updateOrg),
org.updateOrg, org.updateOrg
); );
if (build !== "saas") { if (build !== "saas") {
@@ -90,7 +90,7 @@ if (build !== "saas") {
verifyUserIsOrgOwner, verifyUserIsOrgOwner,
verifyUserHasAction(ActionsEnum.deleteOrg), verifyUserHasAction(ActionsEnum.deleteOrg),
logActionAudit(ActionsEnum.deleteOrg), logActionAudit(ActionsEnum.deleteOrg),
org.deleteOrg, org.deleteOrg
); );
} }
@@ -157,7 +157,7 @@ authenticated.put(
verifyOrgAccess, verifyOrgAccess,
verifyUserHasAction(ActionsEnum.createClient), verifyUserHasAction(ActionsEnum.createClient),
logActionAudit(ActionsEnum.createClient), logActionAudit(ActionsEnum.createClient),
client.createClient, client.createClient
); );
authenticated.delete( authenticated.delete(
@@ -166,7 +166,7 @@ authenticated.delete(
verifyClientAccess, verifyClientAccess,
verifyUserHasAction(ActionsEnum.deleteClient), verifyUserHasAction(ActionsEnum.deleteClient),
logActionAudit(ActionsEnum.deleteClient), logActionAudit(ActionsEnum.deleteClient),
client.deleteClient, client.deleteClient
); );
authenticated.post( authenticated.post(
@@ -175,7 +175,7 @@ authenticated.post(
verifyClientAccess, // this will check if the user has access to the client verifyClientAccess, // this will check if the user has access to the client
verifyUserHasAction(ActionsEnum.updateClient), // this will check if the user has permission to update the client verifyUserHasAction(ActionsEnum.updateClient), // this will check if the user has permission to update the client
logActionAudit(ActionsEnum.updateClient), logActionAudit(ActionsEnum.updateClient),
client.updateClient, client.updateClient
); );
// authenticated.get( // authenticated.get(
@@ -189,14 +189,14 @@ authenticated.post(
verifySiteAccess, verifySiteAccess,
verifyUserHasAction(ActionsEnum.updateSite), verifyUserHasAction(ActionsEnum.updateSite),
logActionAudit(ActionsEnum.updateSite), logActionAudit(ActionsEnum.updateSite),
site.updateSite, site.updateSite
); );
authenticated.delete( authenticated.delete(
"/site/:siteId", "/site/:siteId",
verifySiteAccess, verifySiteAccess,
verifyUserHasAction(ActionsEnum.deleteSite), verifyUserHasAction(ActionsEnum.deleteSite),
logActionAudit(ActionsEnum.deleteSite), logActionAudit(ActionsEnum.deleteSite),
site.deleteSite, site.deleteSite
); );
// TODO: BREAK OUT THESE ACTIONS SO THEY ARE NOT ALL "getSite" // TODO: BREAK OUT THESE ACTIONS SO THEY ARE NOT ALL "getSite"
@@ -216,13 +216,13 @@ authenticated.post(
"/site/:siteId/docker/check", "/site/:siteId/docker/check",
verifySiteAccess, verifySiteAccess,
verifyUserHasAction(ActionsEnum.getSite), verifyUserHasAction(ActionsEnum.getSite),
site.checkDockerSocket, site.checkDockerSocket
); );
authenticated.post( authenticated.post(
"/site/:siteId/docker/trigger", "/site/:siteId/docker/trigger",
verifySiteAccess, verifySiteAccess,
verifyUserHasAction(ActionsEnum.getSite), verifyUserHasAction(ActionsEnum.getSite),
site.triggerFetchContainers, site.triggerFetchContainers
); );
authenticated.get( authenticated.get(
"/site/:siteId/docker/containers", "/site/:siteId/docker/containers",
@@ -238,7 +238,7 @@ authenticated.put(
verifySiteAccess, verifySiteAccess,
verifyUserHasAction(ActionsEnum.createSiteResource), verifyUserHasAction(ActionsEnum.createSiteResource),
logActionAudit(ActionsEnum.createSiteResource), logActionAudit(ActionsEnum.createSiteResource),
siteResource.createSiteResource, siteResource.createSiteResource
); );
authenticated.get( authenticated.get(
@@ -272,7 +272,7 @@ authenticated.post(
verifySiteResourceAccess, verifySiteResourceAccess,
verifyUserHasAction(ActionsEnum.updateSiteResource), verifyUserHasAction(ActionsEnum.updateSiteResource),
logActionAudit(ActionsEnum.updateSiteResource), logActionAudit(ActionsEnum.updateSiteResource),
siteResource.updateSiteResource, siteResource.updateSiteResource
); );
authenticated.delete( authenticated.delete(
@@ -282,7 +282,7 @@ authenticated.delete(
verifySiteResourceAccess, verifySiteResourceAccess,
verifyUserHasAction(ActionsEnum.deleteSiteResource), verifyUserHasAction(ActionsEnum.deleteSiteResource),
logActionAudit(ActionsEnum.deleteSiteResource), logActionAudit(ActionsEnum.deleteSiteResource),
siteResource.deleteSiteResource, siteResource.deleteSiteResource
); );
authenticated.put( authenticated.put(
@@ -290,7 +290,7 @@ authenticated.put(
verifyOrgAccess, verifyOrgAccess,
verifyUserHasAction(ActionsEnum.createResource), verifyUserHasAction(ActionsEnum.createResource),
logActionAudit(ActionsEnum.createResource), logActionAudit(ActionsEnum.createResource),
resource.createResource, resource.createResource
); );
authenticated.get( authenticated.get(
@@ -352,7 +352,7 @@ authenticated.delete(
verifyOrgAccess, verifyOrgAccess,
verifyUserHasAction(ActionsEnum.removeInvitation), verifyUserHasAction(ActionsEnum.removeInvitation),
logActionAudit(ActionsEnum.removeInvitation), logActionAudit(ActionsEnum.removeInvitation),
user.removeInvitation, user.removeInvitation
); );
authenticated.post( authenticated.post(
@@ -360,7 +360,7 @@ authenticated.post(
verifyOrgAccess, verifyOrgAccess,
verifyUserHasAction(ActionsEnum.inviteUser), verifyUserHasAction(ActionsEnum.inviteUser),
logActionAudit(ActionsEnum.inviteUser), logActionAudit(ActionsEnum.inviteUser),
user.inviteUser, user.inviteUser
); // maybe make this /invite/create instead ); // maybe make this /invite/create instead
unauthenticated.post("/invite/accept", user.acceptInvite); // this is supposed to be unauthenticated unauthenticated.post("/invite/accept", user.acceptInvite); // this is supposed to be unauthenticated
@@ -396,14 +396,14 @@ authenticated.post(
verifyResourceAccess, verifyResourceAccess,
verifyUserHasAction(ActionsEnum.updateResource), verifyUserHasAction(ActionsEnum.updateResource),
logActionAudit(ActionsEnum.updateResource), logActionAudit(ActionsEnum.updateResource),
resource.updateResource, resource.updateResource
); );
authenticated.delete( authenticated.delete(
"/resource/:resourceId", "/resource/:resourceId",
verifyResourceAccess, verifyResourceAccess,
verifyUserHasAction(ActionsEnum.deleteResource), verifyUserHasAction(ActionsEnum.deleteResource),
logActionAudit(ActionsEnum.deleteResource), logActionAudit(ActionsEnum.deleteResource),
resource.deleteResource, resource.deleteResource
); );
authenticated.put( authenticated.put(
@@ -411,7 +411,7 @@ authenticated.put(
verifyResourceAccess, verifyResourceAccess,
verifyUserHasAction(ActionsEnum.createTarget), verifyUserHasAction(ActionsEnum.createTarget),
logActionAudit(ActionsEnum.createTarget), logActionAudit(ActionsEnum.createTarget),
target.createTarget, target.createTarget
); );
authenticated.get( authenticated.get(
"/resource/:resourceId/targets", "/resource/:resourceId/targets",
@@ -425,7 +425,7 @@ authenticated.put(
verifyResourceAccess, verifyResourceAccess,
verifyUserHasAction(ActionsEnum.createResourceRule), verifyUserHasAction(ActionsEnum.createResourceRule),
logActionAudit(ActionsEnum.createResourceRule), logActionAudit(ActionsEnum.createResourceRule),
resource.createResourceRule, resource.createResourceRule
); );
authenticated.get( authenticated.get(
"/resource/:resourceId/rules", "/resource/:resourceId/rules",
@@ -438,14 +438,14 @@ authenticated.post(
verifyResourceAccess, verifyResourceAccess,
verifyUserHasAction(ActionsEnum.updateResourceRule), verifyUserHasAction(ActionsEnum.updateResourceRule),
logActionAudit(ActionsEnum.updateResourceRule), logActionAudit(ActionsEnum.updateResourceRule),
resource.updateResourceRule, resource.updateResourceRule
); );
authenticated.delete( authenticated.delete(
"/resource/:resourceId/rule/:ruleId", "/resource/:resourceId/rule/:ruleId",
verifyResourceAccess, verifyResourceAccess,
verifyUserHasAction(ActionsEnum.deleteResourceRule), verifyUserHasAction(ActionsEnum.deleteResourceRule),
logActionAudit(ActionsEnum.deleteResourceRule), logActionAudit(ActionsEnum.deleteResourceRule),
resource.deleteResourceRule, resource.deleteResourceRule
); );
authenticated.get( authenticated.get(
@@ -459,14 +459,14 @@ authenticated.post(
verifyTargetAccess, verifyTargetAccess,
verifyUserHasAction(ActionsEnum.updateTarget), verifyUserHasAction(ActionsEnum.updateTarget),
logActionAudit(ActionsEnum.updateTarget), logActionAudit(ActionsEnum.updateTarget),
target.updateTarget, target.updateTarget
); );
authenticated.delete( authenticated.delete(
"/target/:targetId", "/target/:targetId",
verifyTargetAccess, verifyTargetAccess,
verifyUserHasAction(ActionsEnum.deleteTarget), verifyUserHasAction(ActionsEnum.deleteTarget),
logActionAudit(ActionsEnum.deleteTarget), logActionAudit(ActionsEnum.deleteTarget),
target.deleteTarget, target.deleteTarget
); );
authenticated.put( authenticated.put(
@@ -474,7 +474,7 @@ authenticated.put(
verifyOrgAccess, verifyOrgAccess,
verifyUserHasAction(ActionsEnum.createRole), verifyUserHasAction(ActionsEnum.createRole),
logActionAudit(ActionsEnum.createRole), logActionAudit(ActionsEnum.createRole),
role.createRole, role.createRole
); );
authenticated.get( authenticated.get(
"/org/:orgId/roles", "/org/:orgId/roles",
@@ -500,7 +500,7 @@ authenticated.delete(
verifyRoleAccess, verifyRoleAccess,
verifyUserHasAction(ActionsEnum.deleteRole), verifyUserHasAction(ActionsEnum.deleteRole),
logActionAudit(ActionsEnum.deleteRole), logActionAudit(ActionsEnum.deleteRole),
role.deleteRole, role.deleteRole
); );
authenticated.post( authenticated.post(
"/role/:roleId/add/:userId", "/role/:roleId/add/:userId",
@@ -508,7 +508,7 @@ authenticated.post(
verifyUserAccess, verifyUserAccess,
verifyUserHasAction(ActionsEnum.addUserRole), verifyUserHasAction(ActionsEnum.addUserRole),
logActionAudit(ActionsEnum.addUserRole), logActionAudit(ActionsEnum.addUserRole),
user.addUserRole, user.addUserRole
); );
authenticated.post( authenticated.post(
@@ -517,7 +517,7 @@ authenticated.post(
verifyRoleAccess, verifyRoleAccess,
verifyUserHasAction(ActionsEnum.setResourceRoles), verifyUserHasAction(ActionsEnum.setResourceRoles),
logActionAudit(ActionsEnum.setResourceRoles), logActionAudit(ActionsEnum.setResourceRoles),
resource.setResourceRoles, resource.setResourceRoles
); );
authenticated.post( authenticated.post(
@@ -526,7 +526,7 @@ authenticated.post(
verifySetResourceUsers, verifySetResourceUsers,
verifyUserHasAction(ActionsEnum.setResourceUsers), verifyUserHasAction(ActionsEnum.setResourceUsers),
logActionAudit(ActionsEnum.setResourceUsers), logActionAudit(ActionsEnum.setResourceUsers),
resource.setResourceUsers, resource.setResourceUsers
); );
authenticated.post( authenticated.post(
@@ -534,7 +534,7 @@ authenticated.post(
verifyResourceAccess, verifyResourceAccess,
verifyUserHasAction(ActionsEnum.setResourcePassword), verifyUserHasAction(ActionsEnum.setResourcePassword),
logActionAudit(ActionsEnum.setResourcePassword), logActionAudit(ActionsEnum.setResourcePassword),
resource.setResourcePassword, resource.setResourcePassword
); );
authenticated.post( authenticated.post(
@@ -542,7 +542,7 @@ authenticated.post(
verifyResourceAccess, verifyResourceAccess,
verifyUserHasAction(ActionsEnum.setResourcePincode), verifyUserHasAction(ActionsEnum.setResourcePincode),
logActionAudit(ActionsEnum.setResourcePincode), logActionAudit(ActionsEnum.setResourcePincode),
resource.setResourcePincode, resource.setResourcePincode
); );
authenticated.post( authenticated.post(
@@ -550,7 +550,7 @@ authenticated.post(
verifyResourceAccess, verifyResourceAccess,
verifyUserHasAction(ActionsEnum.setResourceHeaderAuth), verifyUserHasAction(ActionsEnum.setResourceHeaderAuth),
logActionAudit(ActionsEnum.setResourceHeaderAuth), logActionAudit(ActionsEnum.setResourceHeaderAuth),
resource.setResourceHeaderAuth, resource.setResourceHeaderAuth
); );
authenticated.post( authenticated.post(
@@ -558,7 +558,7 @@ authenticated.post(
verifyResourceAccess, verifyResourceAccess,
verifyUserHasAction(ActionsEnum.setResourceWhitelist), verifyUserHasAction(ActionsEnum.setResourceWhitelist),
logActionAudit(ActionsEnum.setResourceWhitelist), logActionAudit(ActionsEnum.setResourceWhitelist),
resource.setResourceWhitelist, resource.setResourceWhitelist
); );
authenticated.get( authenticated.get(
@@ -573,7 +573,7 @@ authenticated.post(
verifyResourceAccess, verifyResourceAccess,
verifyUserHasAction(ActionsEnum.generateAccessToken), verifyUserHasAction(ActionsEnum.generateAccessToken),
logActionAudit(ActionsEnum.generateAccessToken), logActionAudit(ActionsEnum.generateAccessToken),
accessToken.generateAccessToken, accessToken.generateAccessToken
); );
authenticated.delete( authenticated.delete(
@@ -581,7 +581,7 @@ authenticated.delete(
verifyAccessTokenAccess, verifyAccessTokenAccess,
verifyUserHasAction(ActionsEnum.deleteAcessToken), verifyUserHasAction(ActionsEnum.deleteAcessToken),
logActionAudit(ActionsEnum.deleteAcessToken), logActionAudit(ActionsEnum.deleteAcessToken),
accessToken.deleteAccessToken, accessToken.deleteAccessToken
); );
authenticated.get( authenticated.get(
@@ -655,7 +655,7 @@ authenticated.put(
verifyOrgAccess, verifyOrgAccess,
verifyUserHasAction(ActionsEnum.createOrgUser), verifyUserHasAction(ActionsEnum.createOrgUser),
logActionAudit(ActionsEnum.createOrgUser), logActionAudit(ActionsEnum.createOrgUser),
user.createOrgUser, user.createOrgUser
); );
authenticated.post( authenticated.post(
@@ -664,7 +664,7 @@ authenticated.post(
verifyUserAccess, verifyUserAccess,
verifyUserHasAction(ActionsEnum.updateOrgUser), verifyUserHasAction(ActionsEnum.updateOrgUser),
logActionAudit(ActionsEnum.updateOrgUser), logActionAudit(ActionsEnum.updateOrgUser),
user.updateOrgUser, user.updateOrgUser
); );
authenticated.get("/org/:orgId/user/:userId", verifyOrgAccess, user.getOrgUser); authenticated.get("/org/:orgId/user/:userId", verifyOrgAccess, user.getOrgUser);
@@ -688,7 +688,7 @@ authenticated.delete(
verifyUserAccess, verifyUserAccess,
verifyUserHasAction(ActionsEnum.removeUser), verifyUserHasAction(ActionsEnum.removeUser),
logActionAudit(ActionsEnum.removeUser), logActionAudit(ActionsEnum.removeUser),
user.removeUserOrg, user.removeUserOrg
); );
// authenticated.put( // authenticated.put(
@@ -819,7 +819,7 @@ authenticated.post(
verifyApiKeyAccess, verifyApiKeyAccess,
verifyUserHasAction(ActionsEnum.setApiKeyActions), verifyUserHasAction(ActionsEnum.setApiKeyActions),
logActionAudit(ActionsEnum.setApiKeyActions), logActionAudit(ActionsEnum.setApiKeyActions),
apiKeys.setApiKeyActions, apiKeys.setApiKeyActions
); );
authenticated.get( authenticated.get(
@@ -835,7 +835,7 @@ authenticated.put(
verifyOrgAccess, verifyOrgAccess,
verifyUserHasAction(ActionsEnum.createApiKey), verifyUserHasAction(ActionsEnum.createApiKey),
logActionAudit(ActionsEnum.createApiKey), logActionAudit(ActionsEnum.createApiKey),
apiKeys.createOrgApiKey, apiKeys.createOrgApiKey
); );
authenticated.delete( authenticated.delete(
@@ -844,7 +844,7 @@ authenticated.delete(
verifyApiKeyAccess, verifyApiKeyAccess,
verifyUserHasAction(ActionsEnum.deleteApiKey), verifyUserHasAction(ActionsEnum.deleteApiKey),
logActionAudit(ActionsEnum.deleteApiKey), logActionAudit(ActionsEnum.deleteApiKey),
apiKeys.deleteOrgApiKey, apiKeys.deleteOrgApiKey
); );
authenticated.get( authenticated.get(
@@ -860,7 +860,7 @@ authenticated.put(
verifyOrgAccess, verifyOrgAccess,
verifyUserHasAction(ActionsEnum.createOrgDomain), verifyUserHasAction(ActionsEnum.createOrgDomain),
logActionAudit(ActionsEnum.createOrgDomain), logActionAudit(ActionsEnum.createOrgDomain),
domain.createOrgDomain, domain.createOrgDomain
); );
authenticated.post( authenticated.post(
@@ -869,7 +869,7 @@ authenticated.post(
verifyDomainAccess, verifyDomainAccess,
verifyUserHasAction(ActionsEnum.restartOrgDomain), verifyUserHasAction(ActionsEnum.restartOrgDomain),
logActionAudit(ActionsEnum.restartOrgDomain), logActionAudit(ActionsEnum.restartOrgDomain),
domain.restartOrgDomain, domain.restartOrgDomain
); );
authenticated.delete( authenticated.delete(
@@ -878,7 +878,7 @@ authenticated.delete(
verifyDomainAccess, verifyDomainAccess,
verifyUserHasAction(ActionsEnum.deleteOrgDomain), verifyUserHasAction(ActionsEnum.deleteOrgDomain),
logActionAudit(ActionsEnum.deleteOrgDomain), logActionAudit(ActionsEnum.deleteOrgDomain),
domain.deleteAccountDomain, domain.deleteAccountDomain
); );
authenticated.get( authenticated.get(
@@ -1237,4 +1237,4 @@ authRouter.delete(
store: createStore() store: createStore()
}), }),
auth.deleteSecurityKey auth.deleteSecurityKey
); );

View File

@@ -1,16 +1,11 @@
import { internal } from "@app/lib/api";
import { authCookieHeader } from "@app/lib/api/cookies";
import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
import { HorizontalTabs } from "@app/components/HorizontalTabs";
import { verifySession } from "@app/lib/auth/verifySession"; import { verifySession } from "@app/lib/auth/verifySession";
import OrgProvider from "@app/providers/OrgProvider"; import OrgProvider from "@app/providers/OrgProvider";
import OrgUserProvider from "@app/providers/OrgUserProvider"; import OrgUserProvider from "@app/providers/OrgUserProvider";
import { GetOrgResponse } from "@server/routers/org";
import { GetOrgUserResponse } from "@server/routers/user";
import { AxiosResponse } from "axios";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { cache } from "react"; import { getTranslations } from "next-intl/server";
import { getTranslations } from 'next-intl/server'; import { getCachedOrgUser } from "@app/lib/api/getCachedOrgUser";
import { getCachedOrg } from "@app/lib/api/getCachedOrg";
type BillingSettingsProps = { type BillingSettingsProps = {
children: React.ReactNode; children: React.ReactNode;
@@ -19,12 +14,11 @@ type BillingSettingsProps = {
export default async function BillingSettingsPage({ export default async function BillingSettingsPage({
children, children,
params, params
}: BillingSettingsProps) { }: BillingSettingsProps) {
const { orgId } = await params; const { orgId } = await params;
const getUser = cache(verifySession); const user = await verifySession();
const user = await getUser();
if (!user) { if (!user) {
redirect(`/`); redirect(`/`);
@@ -32,13 +26,7 @@ export default async function BillingSettingsPage({
let orgUser = null; let orgUser = null;
try { try {
const getOrgUser = cache(async () => const res = await getCachedOrgUser(orgId, user.userId);
internal.get<AxiosResponse<GetOrgUserResponse>>(
`/org/${orgId}/user/${user.userId}`,
await authCookieHeader(),
),
);
const res = await getOrgUser();
orgUser = res.data.data; orgUser = res.data.data;
} catch { } catch {
redirect(`/${orgId}`); redirect(`/${orgId}`);
@@ -46,13 +34,7 @@ export default async function BillingSettingsPage({
let org = null; let org = null;
try { try {
const getOrg = cache(async () => const res = await getCachedOrg(orgId);
internal.get<AxiosResponse<GetOrgResponse>>(
`/org/${orgId}`,
await authCookieHeader(),
),
);
const res = await getOrg();
org = res.data.data; org = res.data.data;
} catch { } catch {
redirect(`/${orgId}`); redirect(`/${orgId}`);
@@ -65,11 +47,11 @@ export default async function BillingSettingsPage({
<OrgProvider org={org}> <OrgProvider org={org}>
<OrgUserProvider orgUser={orgUser}> <OrgUserProvider orgUser={orgUser}>
<SettingsSectionTitle <SettingsSectionTitle
title={t('billing')} title={t("billing")}
description={t('orgBillingDescription')} description={t("orgBillingDescription")}
/> />
{children} {children}
</OrgUserProvider> </OrgUserProvider>
</OrgProvider> </OrgProvider>
</> </>

View File

@@ -1,5 +1,11 @@
import AuthPageCustomizationForm from "@app/components/AuthPagesCustomizationForm"; import AuthPageCustomizationForm from "@app/components/AuthPagesCustomizationForm";
import { SettingsContainer } from "@app/components/Settings"; import { SettingsContainer } from "@app/components/Settings";
import { getCachedSubscription } from "@app/lib/api/getCachedSubscription";
import { pullEnv } from "@app/lib/pullEnv";
import { build } from "@server/build";
import { TierId } from "@server/lib/billing/tiers";
import type { GetOrgTierResponse } from "@server/routers/billing/types";
import { redirect } from "next/navigation";
export interface AuthPageProps { export interface AuthPageProps {
params: Promise<{ orgId: string }>; params: Promise<{ orgId: string }>;
@@ -7,6 +13,21 @@ export interface AuthPageProps {
export default async function AuthPage(props: AuthPageProps) { export default async function AuthPage(props: AuthPageProps) {
const orgId = (await props.params).orgId; const orgId = (await props.params).orgId;
const env = pullEnv();
let subscriptionStatus: GetOrgTierResponse | null = null;
try {
const subRes = await getCachedSubscription(orgId);
subscriptionStatus = subRes.data.data;
} catch {}
const subscribed =
build === "enterprise"
? true
: subscriptionStatus?.tier === TierId.STANDARD;
if (!subscribed) {
redirect(env.app.dashboardUrl);
}
return ( return (
<SettingsContainer> <SettingsContainer>
<AuthPageCustomizationForm orgId={orgId} /> <AuthPageCustomizationForm orgId={orgId} />

View File

@@ -9,8 +9,14 @@ import { GetOrgResponse } from "@server/routers/org";
import { GetOrgUserResponse } from "@server/routers/user"; import { GetOrgUserResponse } from "@server/routers/user";
import { AxiosResponse } from "axios"; import { AxiosResponse } from "axios";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { cache } from "react";
import { getTranslations } from "next-intl/server"; import { getTranslations } from "next-intl/server";
import { getCachedOrg } from "@app/lib/api/getCachedOrg";
import { getCachedOrgUser } from "@app/lib/api/getCachedOrgUser";
import { GetOrgTierResponse } from "@server/routers/billing/types";
import { getCachedSubscription } from "@app/lib/api/getCachedSubscription";
import { build } from "@server/build";
import { TierId } from "@server/lib/billing/tiers";
type GeneralSettingsProps = { type GeneralSettingsProps = {
children: React.ReactNode; children: React.ReactNode;
@@ -23,8 +29,7 @@ export default async function GeneralSettingsPage({
}: GeneralSettingsProps) { }: GeneralSettingsProps) {
const { orgId } = await params; const { orgId } = await params;
const getUser = cache(verifySession); const user = await verifySession();
const user = await getUser();
if (!user) { if (!user) {
redirect(`/`); redirect(`/`);
@@ -32,13 +37,7 @@ export default async function GeneralSettingsPage({
let orgUser = null; let orgUser = null;
try { try {
const getOrgUser = cache(async () => const res = await getCachedOrgUser(orgId, user.userId);
internal.get<AxiosResponse<GetOrgUserResponse>>(
`/org/${orgId}/user/${user.userId}`,
await authCookieHeader()
)
);
const res = await getOrgUser();
orgUser = res.data.data; orgUser = res.data.data;
} catch { } catch {
redirect(`/${orgId}`); redirect(`/${orgId}`);
@@ -46,18 +45,22 @@ export default async function GeneralSettingsPage({
let org = null; let org = null;
try { try {
const getOrg = cache(async () => const res = await getCachedOrg(orgId);
internal.get<AxiosResponse<GetOrgResponse>>(
`/org/${orgId}`,
await authCookieHeader()
)
);
const res = await getOrg();
org = res.data.data; org = res.data.data;
} catch { } catch {
redirect(`/${orgId}`); redirect(`/${orgId}`);
} }
let subscriptionStatus: GetOrgTierResponse | null = null;
try {
const subRes = await getCachedSubscription(orgId);
subscriptionStatus = subRes.data.data;
} catch {}
const subscribed =
build === "enterprise"
? true
: subscriptionStatus?.tier === TierId.STANDARD;
const t = await getTranslations(); const t = await getTranslations();
const navItems: TabItem[] = [ const navItems: TabItem[] = [
@@ -65,12 +68,14 @@ export default async function GeneralSettingsPage({
title: t("general"), title: t("general"),
href: `/{orgId}/settings/general`, href: `/{orgId}/settings/general`,
exact: true exact: true
},
{
title: t("authPages"),
href: `/{orgId}/settings/general/auth-pages`
} }
]; ];
if (subscribed) {
navItems.push({
title: t("authPage"),
href: `/{orgId}/settings/general/auth-pages`
});
}
return ( return (
<> <>

View File

@@ -0,0 +1,13 @@
import type { GetOrgResponse } from "@server/routers/org";
import type { AxiosResponse } from "axios";
import { cache } from "react";
import { authCookieHeader } from "./cookies";
import { internal } from ".";
import type { GetOrgUserResponse } from "@server/routers/user";
export const getCachedOrgUser = cache(async (orgId: string, userId: string) =>
internal.get<AxiosResponse<GetOrgUserResponse>>(
`/org/${orgId}/user/${userId}`,
await authCookieHeader()
)
);

View File

@@ -0,0 +1,8 @@
import type { AxiosResponse } from "axios";
import { cache } from "react";
import { priv } from ".";
import type { GetOrgTierResponse } from "@server/routers/billing/types";
export const getCachedSubscription = cache(async (orgId: string) =>
priv.get<AxiosResponse<GetOrgTierResponse>>(`/org/${orgId}/billing/tier`)
);

View File

@@ -60,4 +60,3 @@ export const priv = axios.create({
}); });
export * from "./formatAxiosError"; export * from "./formatAxiosError";

View File

@@ -3,9 +3,10 @@ import { authCookieHeader } from "@app/lib/api/cookies";
import { GetUserResponse } from "@server/routers/user"; import { GetUserResponse } from "@server/routers/user";
import { AxiosResponse } from "axios"; import { AxiosResponse } from "axios";
import { pullEnv } from "../pullEnv"; import { pullEnv } from "../pullEnv";
import { cache } from "react";
export async function verifySession({ export const verifySession = cache(async function ({
skipCheckVerifyEmail, skipCheckVerifyEmail
}: { }: {
skipCheckVerifyEmail?: boolean; skipCheckVerifyEmail?: boolean;
} = {}): Promise<GetUserResponse | null> { } = {}): Promise<GetUserResponse | null> {
@@ -14,7 +15,7 @@ export async function verifySession({
try { try {
const res = await internal.get<AxiosResponse<GetUserResponse>>( const res = await internal.get<AxiosResponse<GetUserResponse>>(
"/user", "/user",
await authCookieHeader(), await authCookieHeader()
); );
const user = res.data.data; const user = res.data.data;
@@ -35,4 +36,4 @@ export async function verifySession({
} catch (e) { } catch (e) {
return null; return null;
} }
} });