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",
"resourceErrorDelte": "Error deleting resource",
"authentication": "Authentication",
"authPages": "Auth Page",
"protected": "Protected",
"notProtected": "Not Protected",
"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",
applyBlueprint = "applyBlueprint",
viewLogs = "viewLogs",
exportLogs = "exportLogs"
exportLogs = "exportLogs",
updateOrgAuthPage = "updateOrgAuthPage",
getOrgAuthPage = "getOrgAuthPage"
}
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 loginPage from "#private/routers/loginPage";
import * as orgIdp from "#private/routers/orgIdp";
import * as authPage from "#private/routers/authPage";
import * as domain from "#private/routers/domain";
import * as auth from "#private/routers/auth";
import * as license from "#private/routers/license";
@@ -403,3 +404,23 @@ authenticated.get(
logActionAudit(ActionsEnum.exportLogs),
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,
verifyUserHasAction(ActionsEnum.updateOrg),
logActionAudit(ActionsEnum.updateOrg),
org.updateOrg,
org.updateOrg
);
if (build !== "saas") {
@@ -90,7 +90,7 @@ if (build !== "saas") {
verifyUserIsOrgOwner,
verifyUserHasAction(ActionsEnum.deleteOrg),
logActionAudit(ActionsEnum.deleteOrg),
org.deleteOrg,
org.deleteOrg
);
}
@@ -157,7 +157,7 @@ authenticated.put(
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.createClient),
logActionAudit(ActionsEnum.createClient),
client.createClient,
client.createClient
);
authenticated.delete(
@@ -166,7 +166,7 @@ authenticated.delete(
verifyClientAccess,
verifyUserHasAction(ActionsEnum.deleteClient),
logActionAudit(ActionsEnum.deleteClient),
client.deleteClient,
client.deleteClient
);
authenticated.post(
@@ -175,7 +175,7 @@ authenticated.post(
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
logActionAudit(ActionsEnum.updateClient),
client.updateClient,
client.updateClient
);
// authenticated.get(
@@ -189,14 +189,14 @@ authenticated.post(
verifySiteAccess,
verifyUserHasAction(ActionsEnum.updateSite),
logActionAudit(ActionsEnum.updateSite),
site.updateSite,
site.updateSite
);
authenticated.delete(
"/site/:siteId",
verifySiteAccess,
verifyUserHasAction(ActionsEnum.deleteSite),
logActionAudit(ActionsEnum.deleteSite),
site.deleteSite,
site.deleteSite
);
// TODO: BREAK OUT THESE ACTIONS SO THEY ARE NOT ALL "getSite"
@@ -216,13 +216,13 @@ authenticated.post(
"/site/:siteId/docker/check",
verifySiteAccess,
verifyUserHasAction(ActionsEnum.getSite),
site.checkDockerSocket,
site.checkDockerSocket
);
authenticated.post(
"/site/:siteId/docker/trigger",
verifySiteAccess,
verifyUserHasAction(ActionsEnum.getSite),
site.triggerFetchContainers,
site.triggerFetchContainers
);
authenticated.get(
"/site/:siteId/docker/containers",
@@ -238,7 +238,7 @@ authenticated.put(
verifySiteAccess,
verifyUserHasAction(ActionsEnum.createSiteResource),
logActionAudit(ActionsEnum.createSiteResource),
siteResource.createSiteResource,
siteResource.createSiteResource
);
authenticated.get(
@@ -272,7 +272,7 @@ authenticated.post(
verifySiteResourceAccess,
verifyUserHasAction(ActionsEnum.updateSiteResource),
logActionAudit(ActionsEnum.updateSiteResource),
siteResource.updateSiteResource,
siteResource.updateSiteResource
);
authenticated.delete(
@@ -282,7 +282,7 @@ authenticated.delete(
verifySiteResourceAccess,
verifyUserHasAction(ActionsEnum.deleteSiteResource),
logActionAudit(ActionsEnum.deleteSiteResource),
siteResource.deleteSiteResource,
siteResource.deleteSiteResource
);
authenticated.put(
@@ -290,7 +290,7 @@ authenticated.put(
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.createResource),
logActionAudit(ActionsEnum.createResource),
resource.createResource,
resource.createResource
);
authenticated.get(
@@ -352,7 +352,7 @@ authenticated.delete(
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.removeInvitation),
logActionAudit(ActionsEnum.removeInvitation),
user.removeInvitation,
user.removeInvitation
);
authenticated.post(
@@ -360,7 +360,7 @@ authenticated.post(
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.inviteUser),
logActionAudit(ActionsEnum.inviteUser),
user.inviteUser,
user.inviteUser
); // maybe make this /invite/create instead
unauthenticated.post("/invite/accept", user.acceptInvite); // this is supposed to be unauthenticated
@@ -396,14 +396,14 @@ authenticated.post(
verifyResourceAccess,
verifyUserHasAction(ActionsEnum.updateResource),
logActionAudit(ActionsEnum.updateResource),
resource.updateResource,
resource.updateResource
);
authenticated.delete(
"/resource/:resourceId",
verifyResourceAccess,
verifyUserHasAction(ActionsEnum.deleteResource),
logActionAudit(ActionsEnum.deleteResource),
resource.deleteResource,
resource.deleteResource
);
authenticated.put(
@@ -411,7 +411,7 @@ authenticated.put(
verifyResourceAccess,
verifyUserHasAction(ActionsEnum.createTarget),
logActionAudit(ActionsEnum.createTarget),
target.createTarget,
target.createTarget
);
authenticated.get(
"/resource/:resourceId/targets",
@@ -425,7 +425,7 @@ authenticated.put(
verifyResourceAccess,
verifyUserHasAction(ActionsEnum.createResourceRule),
logActionAudit(ActionsEnum.createResourceRule),
resource.createResourceRule,
resource.createResourceRule
);
authenticated.get(
"/resource/:resourceId/rules",
@@ -438,14 +438,14 @@ authenticated.post(
verifyResourceAccess,
verifyUserHasAction(ActionsEnum.updateResourceRule),
logActionAudit(ActionsEnum.updateResourceRule),
resource.updateResourceRule,
resource.updateResourceRule
);
authenticated.delete(
"/resource/:resourceId/rule/:ruleId",
verifyResourceAccess,
verifyUserHasAction(ActionsEnum.deleteResourceRule),
logActionAudit(ActionsEnum.deleteResourceRule),
resource.deleteResourceRule,
resource.deleteResourceRule
);
authenticated.get(
@@ -459,14 +459,14 @@ authenticated.post(
verifyTargetAccess,
verifyUserHasAction(ActionsEnum.updateTarget),
logActionAudit(ActionsEnum.updateTarget),
target.updateTarget,
target.updateTarget
);
authenticated.delete(
"/target/:targetId",
verifyTargetAccess,
verifyUserHasAction(ActionsEnum.deleteTarget),
logActionAudit(ActionsEnum.deleteTarget),
target.deleteTarget,
target.deleteTarget
);
authenticated.put(
@@ -474,7 +474,7 @@ authenticated.put(
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.createRole),
logActionAudit(ActionsEnum.createRole),
role.createRole,
role.createRole
);
authenticated.get(
"/org/:orgId/roles",
@@ -500,7 +500,7 @@ authenticated.delete(
verifyRoleAccess,
verifyUserHasAction(ActionsEnum.deleteRole),
logActionAudit(ActionsEnum.deleteRole),
role.deleteRole,
role.deleteRole
);
authenticated.post(
"/role/:roleId/add/:userId",
@@ -508,7 +508,7 @@ authenticated.post(
verifyUserAccess,
verifyUserHasAction(ActionsEnum.addUserRole),
logActionAudit(ActionsEnum.addUserRole),
user.addUserRole,
user.addUserRole
);
authenticated.post(
@@ -517,7 +517,7 @@ authenticated.post(
verifyRoleAccess,
verifyUserHasAction(ActionsEnum.setResourceRoles),
logActionAudit(ActionsEnum.setResourceRoles),
resource.setResourceRoles,
resource.setResourceRoles
);
authenticated.post(
@@ -526,7 +526,7 @@ authenticated.post(
verifySetResourceUsers,
verifyUserHasAction(ActionsEnum.setResourceUsers),
logActionAudit(ActionsEnum.setResourceUsers),
resource.setResourceUsers,
resource.setResourceUsers
);
authenticated.post(
@@ -534,7 +534,7 @@ authenticated.post(
verifyResourceAccess,
verifyUserHasAction(ActionsEnum.setResourcePassword),
logActionAudit(ActionsEnum.setResourcePassword),
resource.setResourcePassword,
resource.setResourcePassword
);
authenticated.post(
@@ -542,7 +542,7 @@ authenticated.post(
verifyResourceAccess,
verifyUserHasAction(ActionsEnum.setResourcePincode),
logActionAudit(ActionsEnum.setResourcePincode),
resource.setResourcePincode,
resource.setResourcePincode
);
authenticated.post(
@@ -550,7 +550,7 @@ authenticated.post(
verifyResourceAccess,
verifyUserHasAction(ActionsEnum.setResourceHeaderAuth),
logActionAudit(ActionsEnum.setResourceHeaderAuth),
resource.setResourceHeaderAuth,
resource.setResourceHeaderAuth
);
authenticated.post(
@@ -558,7 +558,7 @@ authenticated.post(
verifyResourceAccess,
verifyUserHasAction(ActionsEnum.setResourceWhitelist),
logActionAudit(ActionsEnum.setResourceWhitelist),
resource.setResourceWhitelist,
resource.setResourceWhitelist
);
authenticated.get(
@@ -573,7 +573,7 @@ authenticated.post(
verifyResourceAccess,
verifyUserHasAction(ActionsEnum.generateAccessToken),
logActionAudit(ActionsEnum.generateAccessToken),
accessToken.generateAccessToken,
accessToken.generateAccessToken
);
authenticated.delete(
@@ -581,7 +581,7 @@ authenticated.delete(
verifyAccessTokenAccess,
verifyUserHasAction(ActionsEnum.deleteAcessToken),
logActionAudit(ActionsEnum.deleteAcessToken),
accessToken.deleteAccessToken,
accessToken.deleteAccessToken
);
authenticated.get(
@@ -655,7 +655,7 @@ authenticated.put(
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.createOrgUser),
logActionAudit(ActionsEnum.createOrgUser),
user.createOrgUser,
user.createOrgUser
);
authenticated.post(
@@ -664,7 +664,7 @@ authenticated.post(
verifyUserAccess,
verifyUserHasAction(ActionsEnum.updateOrgUser),
logActionAudit(ActionsEnum.updateOrgUser),
user.updateOrgUser,
user.updateOrgUser
);
authenticated.get("/org/:orgId/user/:userId", verifyOrgAccess, user.getOrgUser);
@@ -688,7 +688,7 @@ authenticated.delete(
verifyUserAccess,
verifyUserHasAction(ActionsEnum.removeUser),
logActionAudit(ActionsEnum.removeUser),
user.removeUserOrg,
user.removeUserOrg
);
// authenticated.put(
@@ -819,7 +819,7 @@ authenticated.post(
verifyApiKeyAccess,
verifyUserHasAction(ActionsEnum.setApiKeyActions),
logActionAudit(ActionsEnum.setApiKeyActions),
apiKeys.setApiKeyActions,
apiKeys.setApiKeyActions
);
authenticated.get(
@@ -835,7 +835,7 @@ authenticated.put(
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.createApiKey),
logActionAudit(ActionsEnum.createApiKey),
apiKeys.createOrgApiKey,
apiKeys.createOrgApiKey
);
authenticated.delete(
@@ -844,7 +844,7 @@ authenticated.delete(
verifyApiKeyAccess,
verifyUserHasAction(ActionsEnum.deleteApiKey),
logActionAudit(ActionsEnum.deleteApiKey),
apiKeys.deleteOrgApiKey,
apiKeys.deleteOrgApiKey
);
authenticated.get(
@@ -860,7 +860,7 @@ authenticated.put(
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.createOrgDomain),
logActionAudit(ActionsEnum.createOrgDomain),
domain.createOrgDomain,
domain.createOrgDomain
);
authenticated.post(
@@ -869,7 +869,7 @@ authenticated.post(
verifyDomainAccess,
verifyUserHasAction(ActionsEnum.restartOrgDomain),
logActionAudit(ActionsEnum.restartOrgDomain),
domain.restartOrgDomain,
domain.restartOrgDomain
);
authenticated.delete(
@@ -878,7 +878,7 @@ authenticated.delete(
verifyDomainAccess,
verifyUserHasAction(ActionsEnum.deleteOrgDomain),
logActionAudit(ActionsEnum.deleteOrgDomain),
domain.deleteAccountDomain,
domain.deleteAccountDomain
);
authenticated.get(
@@ -1237,4 +1237,4 @@ authRouter.delete(
store: createStore()
}),
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 { HorizontalTabs } from "@app/components/HorizontalTabs";
import { verifySession } from "@app/lib/auth/verifySession";
import OrgProvider from "@app/providers/OrgProvider";
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 { 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 = {
children: React.ReactNode;
@@ -19,12 +14,11 @@ type BillingSettingsProps = {
export default async function BillingSettingsPage({
children,
params,
params
}: BillingSettingsProps) {
const { orgId } = await params;
const getUser = cache(verifySession);
const user = await getUser();
const user = await verifySession();
if (!user) {
redirect(`/`);
@@ -32,13 +26,7 @@ export default async function BillingSettingsPage({
let orgUser = null;
try {
const getOrgUser = cache(async () =>
internal.get<AxiosResponse<GetOrgUserResponse>>(
`/org/${orgId}/user/${user.userId}`,
await authCookieHeader(),
),
);
const res = await getOrgUser();
const res = await getCachedOrgUser(orgId, user.userId);
orgUser = res.data.data;
} catch {
redirect(`/${orgId}`);
@@ -46,13 +34,7 @@ export default async function BillingSettingsPage({
let org = null;
try {
const getOrg = cache(async () =>
internal.get<AxiosResponse<GetOrgResponse>>(
`/org/${orgId}`,
await authCookieHeader(),
),
);
const res = await getOrg();
const res = await getCachedOrg(orgId);
org = res.data.data;
} catch {
redirect(`/${orgId}`);
@@ -65,11 +47,11 @@ export default async function BillingSettingsPage({
<OrgProvider org={org}>
<OrgUserProvider orgUser={orgUser}>
<SettingsSectionTitle
title={t('billing')}
description={t('orgBillingDescription')}
title={t("billing")}
description={t("orgBillingDescription")}
/>
{children}
{children}
</OrgUserProvider>
</OrgProvider>
</>

View File

@@ -1,5 +1,11 @@
import AuthPageCustomizationForm from "@app/components/AuthPagesCustomizationForm";
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 {
params: Promise<{ orgId: string }>;
@@ -7,6 +13,21 @@ export interface AuthPageProps {
export default async function AuthPage(props: AuthPageProps) {
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 (
<SettingsContainer>
<AuthPageCustomizationForm orgId={orgId} />

View File

@@ -9,8 +9,14 @@ import { GetOrgResponse } from "@server/routers/org";
import { GetOrgUserResponse } from "@server/routers/user";
import { AxiosResponse } from "axios";
import { redirect } from "next/navigation";
import { cache } from "react";
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 = {
children: React.ReactNode;
@@ -23,8 +29,7 @@ export default async function GeneralSettingsPage({
}: GeneralSettingsProps) {
const { orgId } = await params;
const getUser = cache(verifySession);
const user = await getUser();
const user = await verifySession();
if (!user) {
redirect(`/`);
@@ -32,13 +37,7 @@ export default async function GeneralSettingsPage({
let orgUser = null;
try {
const getOrgUser = cache(async () =>
internal.get<AxiosResponse<GetOrgUserResponse>>(
`/org/${orgId}/user/${user.userId}`,
await authCookieHeader()
)
);
const res = await getOrgUser();
const res = await getCachedOrgUser(orgId, user.userId);
orgUser = res.data.data;
} catch {
redirect(`/${orgId}`);
@@ -46,18 +45,22 @@ export default async function GeneralSettingsPage({
let org = null;
try {
const getOrg = cache(async () =>
internal.get<AxiosResponse<GetOrgResponse>>(
`/org/${orgId}`,
await authCookieHeader()
)
);
const res = await getOrg();
const res = await getCachedOrg(orgId);
org = res.data.data;
} catch {
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 navItems: TabItem[] = [
@@ -65,12 +68,14 @@ export default async function GeneralSettingsPage({
title: t("general"),
href: `/{orgId}/settings/general`,
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 (
<>

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";

View File

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