mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-27 04:56:38 +00:00
Merge branch 'dev' into multi-role
This commit is contained in:
@@ -29,7 +29,7 @@ registry.registerPath({
|
||||
method: "post",
|
||||
path: "/resource/{resourceId}/whitelist/add",
|
||||
description: "Add a single email to the resource whitelist.",
|
||||
tags: [OpenAPITags.Resource],
|
||||
tags: [OpenAPITags.PublicResource],
|
||||
request: {
|
||||
params: addEmailToResourceWhitelistParamsSchema,
|
||||
body: {
|
||||
|
||||
@@ -29,7 +29,7 @@ registry.registerPath({
|
||||
method: "post",
|
||||
path: "/resource/{resourceId}/roles/add",
|
||||
description: "Add a single role to a resource.",
|
||||
tags: [OpenAPITags.Resource, OpenAPITags.Role],
|
||||
tags: [OpenAPITags.PublicResource, OpenAPITags.Role],
|
||||
request: {
|
||||
params: addRoleToResourceParamsSchema,
|
||||
body: {
|
||||
|
||||
@@ -29,7 +29,7 @@ registry.registerPath({
|
||||
method: "post",
|
||||
path: "/resource/{resourceId}/users/add",
|
||||
description: "Add a single user to a resource.",
|
||||
tags: [OpenAPITags.Resource, OpenAPITags.User],
|
||||
tags: [OpenAPITags.PublicResource, OpenAPITags.User],
|
||||
request: {
|
||||
params: addUserToResourceParamsSchema,
|
||||
body: {
|
||||
|
||||
@@ -79,7 +79,7 @@ registry.registerPath({
|
||||
method: "put",
|
||||
path: "/org/{orgId}/resource",
|
||||
description: "Create a resource.",
|
||||
tags: [OpenAPITags.Org, OpenAPITags.Resource],
|
||||
tags: [OpenAPITags.PublicResource],
|
||||
request: {
|
||||
params: createResourceParamsSchema,
|
||||
body: {
|
||||
@@ -223,6 +223,20 @@ async function createHttpResource(
|
||||
);
|
||||
}
|
||||
|
||||
// Prevent creating resource with same domain as dashboard
|
||||
const dashboardUrl = config.getRawConfig().app.dashboard_url;
|
||||
if (dashboardUrl) {
|
||||
const dashboardHost = new URL(dashboardUrl).hostname;
|
||||
if (fullDomain === dashboardHost) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.CONFLICT,
|
||||
"Resource domain cannot be the same as the dashboard domain"
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (build != "oss") {
|
||||
const existingLoginPages = await db
|
||||
.select()
|
||||
|
||||
@@ -31,7 +31,7 @@ registry.registerPath({
|
||||
method: "put",
|
||||
path: "/resource/{resourceId}/rule",
|
||||
description: "Create a resource rule.",
|
||||
tags: [OpenAPITags.Resource, OpenAPITags.Rule],
|
||||
tags: [OpenAPITags.PublicResource, OpenAPITags.Rule],
|
||||
request: {
|
||||
params: createResourceRuleParamsSchema,
|
||||
body: {
|
||||
|
||||
@@ -22,7 +22,7 @@ registry.registerPath({
|
||||
method: "delete",
|
||||
path: "/resource/{resourceId}",
|
||||
description: "Delete a resource.",
|
||||
tags: [OpenAPITags.Resource],
|
||||
tags: [OpenAPITags.PublicResource],
|
||||
request: {
|
||||
params: deleteResourceSchema
|
||||
},
|
||||
|
||||
@@ -19,7 +19,7 @@ registry.registerPath({
|
||||
method: "delete",
|
||||
path: "/resource/{resourceId}/rule/{ruleId}",
|
||||
description: "Delete a resource rule.",
|
||||
tags: [OpenAPITags.Resource, OpenAPITags.Rule],
|
||||
tags: [OpenAPITags.PublicResource, OpenAPITags.Rule],
|
||||
request: {
|
||||
params: deleteResourceRuleSchema
|
||||
},
|
||||
|
||||
@@ -54,7 +54,7 @@ registry.registerPath({
|
||||
path: "/org/{orgId}/resource/{niceId}",
|
||||
description:
|
||||
"Get a resource by orgId and niceId. NiceId is a readable ID for the resource and unique on a per org basis.",
|
||||
tags: [OpenAPITags.Org, OpenAPITags.Resource],
|
||||
tags: [OpenAPITags.PublicResource],
|
||||
request: {
|
||||
params: z.object({
|
||||
orgId: z.string(),
|
||||
@@ -68,7 +68,7 @@ registry.registerPath({
|
||||
method: "get",
|
||||
path: "/resource/{resourceId}",
|
||||
description: "Get a resource by resourceId.",
|
||||
tags: [OpenAPITags.Resource],
|
||||
tags: [OpenAPITags.PublicResource],
|
||||
request: {
|
||||
params: z.object({
|
||||
resourceId: z.number()
|
||||
|
||||
@@ -31,7 +31,7 @@ registry.registerPath({
|
||||
method: "get",
|
||||
path: "/resource/{resourceId}/whitelist",
|
||||
description: "Get the whitelist of emails for a specific resource.",
|
||||
tags: [OpenAPITags.Resource],
|
||||
tags: [OpenAPITags.PublicResource],
|
||||
request: {
|
||||
params: getResourceWhitelistSchema
|
||||
},
|
||||
|
||||
@@ -33,7 +33,7 @@ registry.registerPath({
|
||||
method: "get",
|
||||
path: "/org/{orgId}/resources-names",
|
||||
description: "List all resource names for an organization.",
|
||||
tags: [OpenAPITags.Org, OpenAPITags.Resource],
|
||||
tags: [OpenAPITags.PublicResource],
|
||||
request: {
|
||||
params: z.object({
|
||||
orgId: z.string()
|
||||
|
||||
@@ -35,7 +35,7 @@ registry.registerPath({
|
||||
method: "get",
|
||||
path: "/resource/{resourceId}/roles",
|
||||
description: "List all roles for a resource.",
|
||||
tags: [OpenAPITags.Resource, OpenAPITags.Role],
|
||||
tags: [OpenAPITags.PublicResource, OpenAPITags.Role],
|
||||
request: {
|
||||
params: listResourceRolesSchema
|
||||
},
|
||||
|
||||
@@ -56,7 +56,7 @@ registry.registerPath({
|
||||
method: "get",
|
||||
path: "/resource/{resourceId}/rules",
|
||||
description: "List rules for a resource.",
|
||||
tags: [OpenAPITags.Resource, OpenAPITags.Rule],
|
||||
tags: [OpenAPITags.PublicResource, OpenAPITags.Rule],
|
||||
request: {
|
||||
params: listResourceRulesParamsSchema,
|
||||
query: listResourceRulesSchema
|
||||
|
||||
@@ -38,7 +38,7 @@ registry.registerPath({
|
||||
method: "get",
|
||||
path: "/resource/{resourceId}/users",
|
||||
description: "List all users for a resource.",
|
||||
tags: [OpenAPITags.Resource, OpenAPITags.User],
|
||||
tags: [OpenAPITags.PublicResource, OpenAPITags.User],
|
||||
request: {
|
||||
params: listResourceUsersSchema
|
||||
},
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
and,
|
||||
asc,
|
||||
count,
|
||||
desc,
|
||||
eq,
|
||||
inArray,
|
||||
isNull,
|
||||
@@ -63,6 +64,26 @@ const listResourcesSchema = z.object({
|
||||
description: "Page number to retrieve"
|
||||
}),
|
||||
query: z.string().optional(),
|
||||
sort_by: z
|
||||
.enum(["name"])
|
||||
.optional()
|
||||
.catch(undefined)
|
||||
.openapi({
|
||||
type: "string",
|
||||
enum: ["name"],
|
||||
description: "Field to sort by"
|
||||
}),
|
||||
order: z
|
||||
.enum(["asc", "desc"])
|
||||
.optional()
|
||||
.default("asc")
|
||||
.catch("asc")
|
||||
.openapi({
|
||||
type: "string",
|
||||
enum: ["asc", "desc"],
|
||||
default: "asc",
|
||||
description: "Sort order"
|
||||
}),
|
||||
enabled: z
|
||||
.enum(["true", "false"])
|
||||
.transform((v) => v === "true")
|
||||
@@ -204,7 +225,7 @@ registry.registerPath({
|
||||
method: "get",
|
||||
path: "/org/{orgId}/resources",
|
||||
description: "List resources for an organization.",
|
||||
tags: [OpenAPITags.Org, OpenAPITags.Resource],
|
||||
tags: [OpenAPITags.PublicResource],
|
||||
request: {
|
||||
params: z.object({
|
||||
orgId: z.string()
|
||||
@@ -229,8 +250,16 @@ export async function listResources(
|
||||
)
|
||||
);
|
||||
}
|
||||
const { page, pageSize, authState, enabled, query, healthStatus } =
|
||||
parsedQuery.data;
|
||||
const {
|
||||
page,
|
||||
pageSize,
|
||||
authState,
|
||||
enabled,
|
||||
query,
|
||||
healthStatus,
|
||||
sort_by,
|
||||
order
|
||||
} = parsedQuery.data;
|
||||
|
||||
const parsedParams = listResourcesParamsSchema.safeParse(req.params);
|
||||
if (!parsedParams.success) {
|
||||
@@ -395,7 +424,13 @@ export async function listResources(
|
||||
baseQuery
|
||||
.limit(pageSize)
|
||||
.offset(pageSize * (page - 1))
|
||||
.orderBy(asc(resources.resourceId)),
|
||||
.orderBy(
|
||||
sort_by
|
||||
? order === "asc"
|
||||
? asc(resources[sort_by])
|
||||
: desc(resources[sort_by])
|
||||
: asc(resources.name)
|
||||
),
|
||||
countQuery
|
||||
]);
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ registry.registerPath({
|
||||
method: "post",
|
||||
path: "/resource/{resourceId}/whitelist/remove",
|
||||
description: "Remove a single email from the resource whitelist.",
|
||||
tags: [OpenAPITags.Resource],
|
||||
tags: [OpenAPITags.PublicResource],
|
||||
request: {
|
||||
params: removeEmailFromResourceWhitelistParamsSchema,
|
||||
body: {
|
||||
|
||||
@@ -29,7 +29,7 @@ registry.registerPath({
|
||||
method: "post",
|
||||
path: "/resource/{resourceId}/roles/remove",
|
||||
description: "Remove a single role from a resource.",
|
||||
tags: [OpenAPITags.Resource, OpenAPITags.Role],
|
||||
tags: [OpenAPITags.PublicResource, OpenAPITags.Role],
|
||||
request: {
|
||||
params: removeRoleFromResourceParamsSchema,
|
||||
body: {
|
||||
|
||||
@@ -29,7 +29,7 @@ registry.registerPath({
|
||||
method: "post",
|
||||
path: "/resource/{resourceId}/users/remove",
|
||||
description: "Remove a single user from a resource.",
|
||||
tags: [OpenAPITags.Resource, OpenAPITags.User],
|
||||
tags: [OpenAPITags.PublicResource, OpenAPITags.User],
|
||||
request: {
|
||||
params: removeUserFromResourceParamsSchema,
|
||||
body: {
|
||||
|
||||
@@ -29,7 +29,7 @@ registry.registerPath({
|
||||
path: "/resource/{resourceId}/header-auth",
|
||||
description:
|
||||
"Set or update the header authentication for a resource. If user and password is not provided, it will remove the header authentication.",
|
||||
tags: [OpenAPITags.Resource],
|
||||
tags: [OpenAPITags.PublicResource],
|
||||
request: {
|
||||
params: setResourceAuthMethodsParamsSchema,
|
||||
body: {
|
||||
|
||||
@@ -25,7 +25,7 @@ registry.registerPath({
|
||||
path: "/resource/{resourceId}/password",
|
||||
description:
|
||||
"Set the password for a resource. Setting the password to null will remove it.",
|
||||
tags: [OpenAPITags.Resource],
|
||||
tags: [OpenAPITags.PublicResource],
|
||||
request: {
|
||||
params: setResourceAuthMethodsParamsSchema,
|
||||
body: {
|
||||
|
||||
@@ -29,7 +29,7 @@ registry.registerPath({
|
||||
path: "/resource/{resourceId}/pincode",
|
||||
description:
|
||||
"Set the PIN code for a resource. Setting the PIN code to null will remove it.",
|
||||
tags: [OpenAPITags.Resource],
|
||||
tags: [OpenAPITags.PublicResource],
|
||||
request: {
|
||||
params: setResourceAuthMethodsParamsSchema,
|
||||
body: {
|
||||
|
||||
@@ -23,7 +23,7 @@ registry.registerPath({
|
||||
path: "/resource/{resourceId}/roles",
|
||||
description:
|
||||
"Set roles for a resource. This will replace all existing roles.",
|
||||
tags: [OpenAPITags.Resource, OpenAPITags.Role],
|
||||
tags: [OpenAPITags.PublicResource, OpenAPITags.Role],
|
||||
request: {
|
||||
params: setResourceRolesParamsSchema,
|
||||
body: {
|
||||
|
||||
@@ -23,7 +23,7 @@ registry.registerPath({
|
||||
path: "/resource/{resourceId}/users",
|
||||
description:
|
||||
"Set users for a resource. This will replace all existing users.",
|
||||
tags: [OpenAPITags.Resource, OpenAPITags.User],
|
||||
tags: [OpenAPITags.PublicResource, OpenAPITags.User],
|
||||
request: {
|
||||
params: setUserResourcesParamsSchema,
|
||||
body: {
|
||||
|
||||
@@ -32,7 +32,7 @@ registry.registerPath({
|
||||
path: "/resource/{resourceId}/whitelist",
|
||||
description:
|
||||
"Set email whitelist for a resource. This will replace all existing emails.",
|
||||
tags: [OpenAPITags.Resource],
|
||||
tags: [OpenAPITags.PublicResource],
|
||||
request: {
|
||||
params: setResourceWhitelistParamsSchema,
|
||||
body: {
|
||||
|
||||
@@ -101,6 +101,49 @@ const updateHttpResourceBodySchema = z
|
||||
{
|
||||
error: "Invalid custom Host Header value. Use domain name format, or save empty to unset custom Host Header."
|
||||
}
|
||||
)
|
||||
.refine(
|
||||
(data) => {
|
||||
if (data.headers) {
|
||||
// HTTP header names must be valid token characters (RFC 7230)
|
||||
const validHeaderName = /^[a-zA-Z0-9!#$%&'*+\-.^_`|~]+$/;
|
||||
return data.headers.every((h) => validHeaderName.test(h.name));
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
error: "Header names may only contain valid HTTP token characters (letters, digits, and !#$%&'*+-.^_`|~)."
|
||||
}
|
||||
)
|
||||
.refine(
|
||||
(data) => {
|
||||
if (data.headers) {
|
||||
// HTTP header values must be visible ASCII or horizontal whitespace, no control chars (RFC 7230)
|
||||
const validHeaderValue = /^[\t\x20-\x7E]*$/;
|
||||
return data.headers.every((h) => validHeaderValue.test(h.value));
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
error: "Header values may only contain printable ASCII characters and horizontal whitespace."
|
||||
}
|
||||
)
|
||||
.refine(
|
||||
(data) => {
|
||||
if (data.headers) {
|
||||
// Reject Traefik template syntax {{word}} in names or values
|
||||
const templatePattern = /\{\{[^}]+\}\}/;
|
||||
return data.headers.every(
|
||||
(h) =>
|
||||
!templatePattern.test(h.name) &&
|
||||
!templatePattern.test(h.value)
|
||||
);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
error: "Header names and values must not contain template expressions such as {{value}}."
|
||||
}
|
||||
);
|
||||
|
||||
export type UpdateResourceResponse = Resource;
|
||||
@@ -136,7 +179,7 @@ registry.registerPath({
|
||||
method: "post",
|
||||
path: "/resource/{resourceId}",
|
||||
description: "Update a resource.",
|
||||
tags: [OpenAPITags.Resource],
|
||||
tags: [OpenAPITags.PublicResource],
|
||||
request: {
|
||||
params: updateResourceParamsSchema,
|
||||
body: {
|
||||
@@ -310,6 +353,20 @@ async function updateHttpResource(
|
||||
);
|
||||
}
|
||||
|
||||
// Prevent updating resource with same domain as dashboard
|
||||
const dashboardUrl = config.getRawConfig().app.dashboard_url;
|
||||
if (dashboardUrl) {
|
||||
const dashboardHost = new URL(dashboardUrl).hostname;
|
||||
if (fullDomain === dashboardHost) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.CONFLICT,
|
||||
"Resource domain cannot be the same as the dashboard domain"
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (build != "oss") {
|
||||
const existingLoginPages = await db
|
||||
.select()
|
||||
|
||||
@@ -38,7 +38,7 @@ registry.registerPath({
|
||||
method: "post",
|
||||
path: "/resource/{resourceId}/rule/{ruleId}",
|
||||
description: "Update a resource rule.",
|
||||
tags: [OpenAPITags.Resource, OpenAPITags.Rule],
|
||||
tags: [OpenAPITags.PublicResource, OpenAPITags.Rule],
|
||||
request: {
|
||||
params: updateResourceRuleParamsSchema,
|
||||
body: {
|
||||
|
||||
Reference in New Issue
Block a user