Merge pull request #1353 from fosrl/dev

1.9.1
This commit is contained in:
Owen Schwartz
2025-08-25 17:13:33 -07:00
committed by GitHub
22 changed files with 246 additions and 16 deletions

View File

@@ -1052,6 +1052,11 @@
"actionUpdateClient": "Update Client",
"actionListClients": "List Clients",
"actionGetClient": "Get Client",
"actionCreateSiteResource": "Create Site Resource",
"actionDeleteSiteResource": "Delete Site Resource",
"actionGetSiteResource": "Get Site Resource",
"actionListSiteResources": "List Site Resources",
"actionUpdateSiteResource": "Update Site Resource",
"noneSelected": "None selected",
"orgNotFound2": "No organizations found.",
"searchProgress": "Search...",

View File

@@ -1052,6 +1052,11 @@
"actionUpdateClient": "Update Client",
"actionListClients": "List Clients",
"actionGetClient": "Get Client",
"actionCreateSiteResource": "Create Site Resource",
"actionDeleteSiteResource": "Delete Site Resource",
"actionGetSiteResource": "Get Site Resource",
"actionListSiteResources": "List Site Resources",
"actionUpdateSiteResource": "Update Site Resource",
"noneSelected": "None selected",
"orgNotFound2": "No organizations found.",
"searchProgress": "Search...",

View File

@@ -1052,6 +1052,11 @@
"actionUpdateClient": "Kunde aktualisieren",
"actionListClients": "Kunden auflisten",
"actionGetClient": "Kunde holen",
"actionCreateSiteResource": "Site-Ressource erstellen",
"actionDeleteSiteResource": "Site-Ressource löschen",
"actionGetSiteResource": "Site-Ressource abrufen",
"actionListSiteResources": "Site-Ressourcen auflisten",
"actionUpdateSiteResource": "Site-Ressource aktualisieren",
"noneSelected": "Keine ausgewählt",
"orgNotFound2": "Keine Organisationen gefunden.",
"searchProgress": "Suche...",

View File

@@ -1052,6 +1052,11 @@
"actionUpdateClient": "Update Client",
"actionListClients": "List Clients",
"actionGetClient": "Get Client",
"actionCreateSiteResource": "Create Site Resource",
"actionDeleteSiteResource": "Delete Site Resource",
"actionGetSiteResource": "Get Site Resource",
"actionListSiteResources": "List Site Resources",
"actionUpdateSiteResource": "Update Site Resource",
"noneSelected": "None selected",
"orgNotFound2": "No organizations found.",
"searchProgress": "Search...",

View File

@@ -1052,6 +1052,11 @@
"actionUpdateClient": "Actualizar cliente",
"actionListClients": "Listar clientes",
"actionGetClient": "Obtener cliente",
"actionCreateSiteResource": "Crear Recurso del Sitio",
"actionDeleteSiteResource": "Eliminar recurso del sitio",
"actionGetSiteResource": "Obtener recurso del sitio",
"actionListSiteResources": "Listar recursos del sitio",
"actionUpdateSiteResource": "Actualizar recurso del sitio",
"noneSelected": "Ninguno seleccionado",
"orgNotFound2": "No se encontraron organizaciones.",
"searchProgress": "Buscar...",

View File

@@ -1052,6 +1052,11 @@
"actionUpdateClient": "Mettre à jour le client",
"actionListClients": "Liste des clients",
"actionGetClient": "Obtenir le client",
"actionCreateSiteResource": "Créer une ressource de site",
"actionDeleteSiteResource": "Supprimer une ressource de site",
"actionGetSiteResource": "Obtenir une ressource de site",
"actionListSiteResources": "Lister les ressources de site",
"actionUpdateSiteResource": "Mettre à jour une ressource de site",
"noneSelected": "Aucune sélection",
"orgNotFound2": "Aucune organisation trouvée.",
"searchProgress": "Rechercher...",

View File

@@ -1052,6 +1052,11 @@
"actionUpdateClient": "Aggiorna Client",
"actionListClients": "Elenco Clienti",
"actionGetClient": "Ottieni Client",
"actionCreateSiteResource": "Crea Risorsa del Sito",
"actionDeleteSiteResource": "Elimina Risorsa del Sito",
"actionGetSiteResource": "Ottieni Risorsa del Sito",
"actionListSiteResources": "Elenca Risorse del Sito",
"actionUpdateSiteResource": "Aggiorna Risorsa del Sito",
"noneSelected": "Nessuna selezione",
"orgNotFound2": "Nessuna organizzazione trovata.",
"searchProgress": "Ricerca...",

View File

@@ -1052,6 +1052,11 @@
"actionUpdateClient": "클라이언트 업데이트",
"actionListClients": "클라이언트 목록",
"actionGetClient": "클라이언트 가져오기",
"actionCreateSiteResource": "사이트 리소스 생성",
"actionDeleteSiteResource": "사이트 리소스 삭제",
"actionGetSiteResource": "사이트 리소스 가져오기",
"actionListSiteResources": "사이트 리소스 목록",
"actionUpdateSiteResource": "사이트 리소스 업데이트",
"noneSelected": "선택된 항목 없음",
"orgNotFound2": "조직이 없습니다.",
"searchProgress": "검색...",

View File

@@ -1052,6 +1052,11 @@
"actionUpdateClient": "Oppdater klient",
"actionListClients": "List klienter",
"actionGetClient": "Hent klient",
"actionCreateSiteResource": "Opprett stedsressurs",
"actionDeleteSiteResource": "Slett Stedsressurs",
"actionGetSiteResource": "Hent Stedsressurs",
"actionListSiteResources": "List opp Stedsressurser",
"actionUpdateSiteResource": "Oppdater Stedsressurs",
"noneSelected": "Ingen valgt",
"orgNotFound2": "Ingen organisasjoner funnet.",
"searchProgress": "Søker...",

View File

@@ -1052,6 +1052,11 @@
"actionUpdateClient": "Klant bijwerken",
"actionListClients": "Lijst klanten",
"actionGetClient": "Client ophalen",
"actionCreateSiteResource": "Sitebron maken",
"actionDeleteSiteResource": "Document verwijderen van site",
"actionGetSiteResource": "Bron van site ophalen",
"actionListSiteResources": "Bronnen van site weergeven",
"actionUpdateSiteResource": "Document bijwerken van site",
"noneSelected": "Niet geselecteerd",
"orgNotFound2": "Geen organisaties gevonden.",
"searchProgress": "Zoeken...",

View File

@@ -1052,6 +1052,11 @@
"actionUpdateClient": "Aktualizuj klienta",
"actionListClients": "Lista klientów",
"actionGetClient": "Pobierz klienta",
"actionCreateSiteResource": "Utwórz zasób witryny",
"actionDeleteSiteResource": "Usuń zasób strony",
"actionGetSiteResource": "Pobierz zasób strony",
"actionListSiteResources": "Lista zasobów strony",
"actionUpdateSiteResource": "Aktualizuj zasób strony",
"noneSelected": "Nie wybrano",
"orgNotFound2": "Nie znaleziono organizacji.",
"searchProgress": "Szukaj...",

View File

@@ -1052,6 +1052,11 @@
"actionUpdateClient": "Atualizar Cliente",
"actionListClients": "Listar Clientes",
"actionGetClient": "Obter Cliente",
"actionCreateSiteResource": "Criar Recurso do Site",
"actionDeleteSiteResource": "Eliminar Recurso do Site",
"actionGetSiteResource": "Obter Recurso do Site",
"actionListSiteResources": "Listar Recursos do Site",
"actionUpdateSiteResource": "Atualizar Recurso do Site",
"noneSelected": "Nenhum selecionado",
"orgNotFound2": "Nenhuma organização encontrada.",
"searchProgress": "Pesquisar...",

View File

@@ -1052,6 +1052,11 @@
"actionUpdateClient": "Обновить Клиента",
"actionListClients": "Список Клиентов",
"actionGetClient": "Получить Клиента",
"actionCreateSiteResource": "Создать ресурс сайта",
"actionDeleteSiteResource": "Удалить ресурс сайта ",
"actionGetSiteResource": "Получить ресурс сайта",
"actionListSiteResources": "Список ресурсов сайта",
"actionUpdateSiteResource": "Обновить ресурс сайта",
"noneSelected": "Ничего не выбрано",
"orgNotFound2": "Организации не найдены.",
"searchProgress": "Поиск...",

View File

@@ -1052,6 +1052,11 @@
"actionUpdateClient": "Müşteri Güncelle",
"actionListClients": "Müşterileri Listele",
"actionGetClient": "Müşteriyi Al",
"actionCreateSiteResource": "Site Kaynağı Oluştur",
"actionDeleteSiteResource": "Site Kaynağını Sil",
"actionGetSiteResource": "Site Kaynağını Al",
"actionListSiteResources": "Site Kaynaklarını Listele",
"actionUpdateSiteResource": "Site Kaynağını Güncelle",
"noneSelected": "Hiçbiri seçili değil",
"orgNotFound2": "Hiçbir organizasyon bulunamadı.",
"searchProgress": "Ara...",

View File

@@ -1052,6 +1052,11 @@
"actionUpdateClient": "更新客户端",
"actionListClients": "列出客户端",
"actionGetClient": "获取客户端",
"actionCreateSiteResource": "创建站点资源",
"actionDeleteSiteResource": "删除站点资源",
"actionGetSiteResource": "获取站点资源",
"actionListSiteResources": "列出站点资源",
"actionUpdateSiteResource": "更新站点资源",
"noneSelected": "未选择",
"orgNotFound2": "未找到组织。",
"searchProgress": "搜索中...",

View File

@@ -11,3 +11,4 @@ export * from "./verifyAccessTokenAccess";
export * from "./verifyApiKeyIsRoot";
export * from "./verifyApiKeyApiKeyAccess";
export * from "./verifyApiKeyClientAccess";
export * from "./verifyApiKeySiteResourceAccess";

View File

@@ -0,0 +1,97 @@
import { Request, Response, NextFunction } from "express";
import { db } from "@server/db";
import { siteResources, apiKeyOrg } from "@server/db";
import { and, eq } from "drizzle-orm";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
export async function verifyApiKeySiteResourceAccess(
req: Request,
res: Response,
next: NextFunction
) {
try {
const apiKey = req.apiKey;
const siteResourceId = parseInt(req.params.siteResourceId);
const siteId = parseInt(req.params.siteId);
const orgId = req.params.orgId;
if (!apiKey) {
return next(
createHttpError(HttpCode.UNAUTHORIZED, "Key not authenticated")
);
}
if (!siteResourceId || !siteId || !orgId) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"Missing required parameters"
)
);
}
if (apiKey.isRoot) {
// Root keys can access any resource in any org
return next();
}
// Check if the site resource exists and belongs to the specified site and org
const [siteResource] = await db
.select()
.from(siteResources)
.where(and(
eq(siteResources.siteResourceId, siteResourceId),
eq(siteResources.siteId, siteId),
eq(siteResources.orgId, orgId)
))
.limit(1);
if (!siteResource) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
"Site resource not found"
)
);
}
// Verify that the API key has access to the organization
if (!req.apiKeyOrg) {
const apiKeyOrgRes = await db
.select()
.from(apiKeyOrg)
.where(
and(
eq(apiKeyOrg.apiKeyId, apiKey.apiKeyId),
eq(apiKeyOrg.orgId, orgId)
)
)
.limit(1);
if (apiKeyOrgRes.length === 0) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"Key does not have access to this organization"
)
);
}
req.apiKeyOrg = apiKeyOrgRes[0];
}
// Attach the siteResource to the request for use in the next middleware/route
// @ts-ignore - Extending Request type
req.siteResource = siteResource;
return next();
} catch (error) {
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"Error verifying site resource access"
)
);
}
}

View File

@@ -9,6 +9,7 @@ import * as client from "./client";
import * as accessToken from "./accessToken";
import * as apiKeys from "./apiKeys";
import * as idp from "./idp";
import * as siteResource from "./siteResource";
import {
verifyApiKey,
verifyApiKeyOrgAccess,
@@ -22,7 +23,8 @@ import {
verifyApiKeyAccessTokenAccess,
verifyApiKeyIsRoot,
verifyApiKeyClientAccess,
verifyClientsEnabled
verifyClientsEnabled,
verifyApiKeySiteResourceAccess
} from "@server/middlewares";
import HttpCode from "@server/types/HttpCode";
import { Router } from "express";
@@ -128,6 +130,69 @@ authenticated.delete(
site.deleteSite
);
authenticated.get(
"/org/:orgId/user-resources",
verifyApiKeyOrgAccess,
resource.getUserResources
);
// Site Resource endpoints
authenticated.put(
"/org/:orgId/site/:siteId/resource",
verifyApiKeyOrgAccess,
verifyApiKeySiteAccess,
verifyApiKeyHasAction(ActionsEnum.createSiteResource),
siteResource.createSiteResource
);
authenticated.get(
"/org/:orgId/site/:siteId/resources",
verifyApiKeyOrgAccess,
verifyApiKeySiteAccess,
verifyApiKeyHasAction(ActionsEnum.listSiteResources),
siteResource.listSiteResources
);
authenticated.get(
"/org/:orgId/site-resources",
verifyApiKeyOrgAccess,
verifyApiKeyHasAction(ActionsEnum.listSiteResources),
siteResource.listAllSiteResourcesByOrg
);
authenticated.get(
"/org/:orgId/site/:siteId/resource/:siteResourceId",
verifyApiKeyOrgAccess,
verifyApiKeySiteAccess,
verifyApiKeySiteResourceAccess,
verifyApiKeyHasAction(ActionsEnum.getSiteResource),
siteResource.getSiteResource
);
authenticated.post(
"/org/:orgId/site/:siteId/resource/:siteResourceId",
verifyApiKeyOrgAccess,
verifyApiKeySiteAccess,
verifyApiKeySiteResourceAccess,
verifyApiKeyHasAction(ActionsEnum.updateSiteResource),
siteResource.updateSiteResource
);
authenticated.delete(
"/org/:orgId/site/:siteId/resource/:siteResourceId",
verifyApiKeyOrgAccess,
verifyApiKeySiteAccess,
verifyApiKeySiteResourceAccess,
verifyApiKeyHasAction(ActionsEnum.deleteSiteResource),
siteResource.deleteSiteResource
);
authenticated.put(
"/org/:orgId/resource",
verifyApiKeyOrgAccess,
verifyApiKeyHasAction(ActionsEnum.createResource),
resource.createResource
);
authenticated.put(
"/org/:orgId/site/:siteId/resource",
verifyApiKeyOrgAccess,

View File

@@ -60,7 +60,7 @@ export type ListRolesResponse = {
registry.registerPath({
method: "get",
path: "/orgs/{orgId}/roles",
path: "/org/{orgId}/roles",
description: "List roles.",
tags: [OpenAPITags.Org, OpenAPITags.Role],
request: {

View File

@@ -777,15 +777,6 @@ export default function Page() {
</SettingsContainer>
<div className="flex justify-end space-x-2 mt-8">
<Button
type="button"
variant="outline"
onClick={() => {
router.push(`/${orgId}/settings/access/users`);
}}
>
{t("cancel")}
</Button>
{userType && dataLoaded && (
<Button
type={inviteLink ? "button" : "submit"}

View File

@@ -51,7 +51,12 @@ function getActionsCategories(root: boolean) {
[t('actionSetResourcePassword')]: "setResourcePassword",
[t('actionSetResourcePincode')]: "setResourcePincode",
[t('actionSetResourceEmailWhitelist')]: "setResourceWhitelist",
[t('actionGetResourceEmailWhitelist')]: "getResourceWhitelist"
[t('actionGetResourceEmailWhitelist')]: "getResourceWhitelist",
[t('actionCreateSiteResource')]: "createSiteResource",
[t('actionDeleteSiteResource')]: "deleteSiteResource",
[t('actionGetSiteResource')]: "getSiteResource",
[t('actionListSiteResources')]: "listSiteResources",
[t('actionUpdateSiteResource')]: "updateSiteResource"
},
Target: {

4
t
View File

@@ -1,4 +0,0 @@
docker buildx build --build-arg DATABASE=sqlite --platform linux/arm64,linux/amd64 -t fosrl/pangolin:latest --push .
docker buildx build --build-arg DATABASE=sqlite --platform linux/arm64,linux/amd64 -t fosrl/pangolin:1.9.0 --push .
docker buildx build --build-arg DATABASE=pg --platform linux/arm64,linux/amd64 -t fosrl/pangolin:postgresql-latest --push .
docker buildx build --build-arg DATABASE=pg --platform linux/arm64,linux/amd64 -t fosrl/pangolin:postgresql-1.9.0 --push .