Compare commits
14 commits
21f1326045
...
1f584bf3e8
Author | SHA1 | Date | |
---|---|---|---|
|
1f584bf3e8 | ||
|
5b0200154a | ||
|
a512148348 | ||
|
d9eccd6c13 | ||
|
492669f68a | ||
|
caded23b51 | ||
|
e9cc48a3ae | ||
|
4ed98c227b | ||
|
f66fb7d4a3 | ||
|
f25990a9a7 | ||
|
21d5b67ef1 | ||
|
198810121c | ||
|
83c0379c6b | ||
|
9a167b5acb |
99 changed files with 559 additions and 825 deletions
32
LICENSE
32
LICENSE
|
@ -1,35 +1,3 @@
|
||||||
Copyright (c) 2025 Fossorial, LLC.
|
|
||||||
|
|
||||||
Portions of this software are licensed as follows:
|
|
||||||
|
|
||||||
* All files that include a header specifying they are licensed under the
|
|
||||||
"Fossorial Commercial License" are governed by the Fossorial Commercial
|
|
||||||
License terms. The specific terms applicable to each customer depend on the
|
|
||||||
commercial license tier agreed upon in writing with Fossorial LLC.
|
|
||||||
Unauthorized use, copying, modification, or distribution is strictly
|
|
||||||
prohibited.
|
|
||||||
|
|
||||||
* All files that include a header specifying they are licensed under the GNU
|
|
||||||
Affero General Public License, Version 3 ("AGPL-3"), are governed by the
|
|
||||||
AGPL-3 terms. A full copy of the AGPL-3 license is provided below. However,
|
|
||||||
these files are also available under the Fossorial Commercial License if a
|
|
||||||
separate commercial license agreement has been executed between the customer
|
|
||||||
and Fossorial LLC.
|
|
||||||
|
|
||||||
* All files without a license header are, by default, licensed under the GNU
|
|
||||||
Affero General Public License, Version 3 (AGPL-3). These files may also be
|
|
||||||
made available under the Fossorial Commercial License upon agreement with
|
|
||||||
Fossorial LLC.
|
|
||||||
|
|
||||||
* All third-party components included in this repository are licensed under
|
|
||||||
their respective original licenses, as provided by their authors.
|
|
||||||
|
|
||||||
Please consult the header of each individual file to determine the applicable
|
|
||||||
license. For AGPL-3 licensed files, dual-licensing under the Fossorial
|
|
||||||
Commercial License is available subject to written agreement with Fossorial
|
|
||||||
LLC.
|
|
||||||
|
|
||||||
|
|
||||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
Version 3, 19 November 2007
|
Version 3, 19 November 2007
|
||||||
|
|
||||||
|
|
|
@ -64,14 +64,14 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
var config Config
|
var config Config
|
||||||
config.DoCrowdsecInstall = false
|
|
||||||
config.Secret = generateRandomSecretKey()
|
|
||||||
|
|
||||||
// check if there is already a config file
|
// check if there is already a config file
|
||||||
if _, err := os.Stat("config/config.yml"); err != nil {
|
if _, err := os.Stat("config/config.yml"); err != nil {
|
||||||
config = collectUserInput(reader)
|
config = collectUserInput(reader)
|
||||||
|
|
||||||
loadVersions(&config)
|
loadVersions(&config)
|
||||||
|
config.DoCrowdsecInstall = false
|
||||||
|
config.Secret = generateRandomSecretKey()
|
||||||
|
|
||||||
if err := createConfigFiles(config); err != nil {
|
if err := createConfigFiles(config); err != nil {
|
||||||
fmt.Printf("Error creating config files: %v\n", err)
|
fmt.Printf("Error creating config files: %v\n", err)
|
||||||
|
|
BIN
newt
Executable file
BIN
newt
Executable file
Binary file not shown.
|
@ -6,7 +6,7 @@ import { createNextServer } from "./nextServer";
|
||||||
import { createInternalServer } from "./internalServer";
|
import { createInternalServer } from "./internalServer";
|
||||||
import { ApiKey, ApiKeyOrg, Session, User, UserOrg } from "./db/schemas";
|
import { ApiKey, ApiKeyOrg, Session, User, UserOrg } from "./db/schemas";
|
||||||
import { createIntegrationApiServer } from "./integrationApiServer";
|
import { createIntegrationApiServer } from "./integrationApiServer";
|
||||||
import license from "./license/license.js";
|
import config from "@server/lib/config";
|
||||||
|
|
||||||
async function startServers() {
|
async function startServers() {
|
||||||
await runSetupFunctions();
|
await runSetupFunctions();
|
||||||
|
@ -17,7 +17,7 @@ async function startServers() {
|
||||||
const nextServer = await createNextServer();
|
const nextServer = await createNextServer();
|
||||||
|
|
||||||
let integrationServer;
|
let integrationServer;
|
||||||
if (await license.isUnlocked()) {
|
if (config.getRawConfig().flags?.enable_integration_api) {
|
||||||
integrationServer = createIntegrationApiServer();
|
integrationServer = createIntegrationApiServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import cors from "cors";
|
import cors from "cors";
|
||||||
import cookieParser from "cookie-parser";
|
import cookieParser from "cookie-parser";
|
||||||
|
@ -11,7 +6,6 @@ import logger from "@server/logger";
|
||||||
import {
|
import {
|
||||||
errorHandlerMiddleware,
|
errorHandlerMiddleware,
|
||||||
notFoundMiddleware,
|
notFoundMiddleware,
|
||||||
verifyValidLicense
|
|
||||||
} from "@server/middlewares";
|
} from "@server/middlewares";
|
||||||
import { authenticated, unauthenticated } from "@server/routers/integration";
|
import { authenticated, unauthenticated } from "@server/routers/integration";
|
||||||
import { logIncomingMiddleware } from "./middlewares/logIncoming";
|
import { logIncomingMiddleware } from "./middlewares/logIncoming";
|
||||||
|
@ -26,8 +20,6 @@ const externalPort = config.getRawConfig().server.integration_port;
|
||||||
export function createIntegrationApiServer() {
|
export function createIntegrationApiServer() {
|
||||||
const apiServer = express();
|
const apiServer = express();
|
||||||
|
|
||||||
apiServer.use(verifyValidLicense);
|
|
||||||
|
|
||||||
if (config.getRawConfig().server.trust_proxy) {
|
if (config.getRawConfig().server.trust_proxy) {
|
||||||
apiServer.set("trust proxy", 1);
|
apiServer.set("trust proxy", 1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,9 +29,12 @@ const configSchema = z.object({
|
||||||
.optional()
|
.optional()
|
||||||
.pipe(z.string().url())
|
.pipe(z.string().url())
|
||||||
.transform((url) => url.toLowerCase()),
|
.transform((url) => url.toLowerCase()),
|
||||||
log_level: z.enum(["debug", "info", "warn", "error"]),
|
log_level: z
|
||||||
save_logs: z.boolean(),
|
.enum(["debug", "info", "warn", "error"])
|
||||||
log_failed_attempts: z.boolean().optional()
|
.optional()
|
||||||
|
.default("info"),
|
||||||
|
save_logs: z.boolean().optional().default(false),
|
||||||
|
log_failed_attempts: z.boolean().optional().default(false)
|
||||||
}),
|
}),
|
||||||
domains: z
|
domains: z
|
||||||
.record(
|
.record(
|
||||||
|
@ -41,8 +44,8 @@ const configSchema = z.object({
|
||||||
.string()
|
.string()
|
||||||
.nonempty("base_domain must not be empty")
|
.nonempty("base_domain must not be empty")
|
||||||
.transform((url) => url.toLowerCase()),
|
.transform((url) => url.toLowerCase()),
|
||||||
cert_resolver: z.string().optional(),
|
cert_resolver: z.string().optional().default("letsencrypt"),
|
||||||
prefer_wildcard_cert: z.boolean().optional()
|
prefer_wildcard_cert: z.boolean().optional().default(false)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.refine(
|
.refine(
|
||||||
|
@ -62,19 +65,42 @@ const configSchema = z.object({
|
||||||
server: z.object({
|
server: z.object({
|
||||||
integration_port: portSchema
|
integration_port: portSchema
|
||||||
.optional()
|
.optional()
|
||||||
|
.default(3003)
|
||||||
.transform(stoi)
|
.transform(stoi)
|
||||||
.pipe(portSchema.optional()),
|
.pipe(portSchema.optional()),
|
||||||
external_port: portSchema.optional().transform(stoi).pipe(portSchema),
|
external_port: portSchema
|
||||||
internal_port: portSchema.optional().transform(stoi).pipe(portSchema),
|
.optional()
|
||||||
next_port: portSchema.optional().transform(stoi).pipe(portSchema),
|
.default(3000)
|
||||||
internal_hostname: z.string().transform((url) => url.toLowerCase()),
|
.transform(stoi)
|
||||||
session_cookie_name: z.string(),
|
.pipe(portSchema),
|
||||||
resource_access_token_param: z.string(),
|
internal_port: portSchema
|
||||||
resource_access_token_headers: z.object({
|
.optional()
|
||||||
id: z.string(),
|
.default(3001)
|
||||||
token: z.string()
|
.transform(stoi)
|
||||||
}),
|
.pipe(portSchema),
|
||||||
resource_session_request_param: z.string(),
|
next_port: portSchema
|
||||||
|
.optional()
|
||||||
|
.default(3002)
|
||||||
|
.transform(stoi)
|
||||||
|
.pipe(portSchema),
|
||||||
|
internal_hostname: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.default("pangolin")
|
||||||
|
.transform((url) => url.toLowerCase()),
|
||||||
|
session_cookie_name: z.string().optional().default("p_session_token"),
|
||||||
|
resource_access_token_param: z.string().optional().default("p_token"),
|
||||||
|
resource_access_token_headers: z
|
||||||
|
.object({
|
||||||
|
id: z.string().optional().default("P-Access-Token-Id"),
|
||||||
|
token: z.string().optional().default("P-Access-Token")
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
.default({}),
|
||||||
|
resource_session_request_param: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.default("resource_session_request_param"),
|
||||||
dashboard_session_length_hours: z
|
dashboard_session_length_hours: z
|
||||||
.number()
|
.number()
|
||||||
.positive()
|
.positive()
|
||||||
|
@ -102,35 +128,61 @@ const configSchema = z.object({
|
||||||
.transform(getEnvOrYaml("SERVER_SECRET"))
|
.transform(getEnvOrYaml("SERVER_SECRET"))
|
||||||
.pipe(z.string().min(8))
|
.pipe(z.string().min(8))
|
||||||
}),
|
}),
|
||||||
traefik: z.object({
|
traefik: z
|
||||||
http_entrypoint: z.string(),
|
.object({
|
||||||
https_entrypoint: z.string().optional(),
|
http_entrypoint: z.string().optional().default("web"),
|
||||||
|
https_entrypoint: z.string().optional().default("websecure"),
|
||||||
additional_middlewares: z.array(z.string()).optional()
|
additional_middlewares: z.array(z.string()).optional()
|
||||||
}),
|
})
|
||||||
gerbil: z.object({
|
.optional()
|
||||||
start_port: portSchema.optional().transform(stoi).pipe(portSchema),
|
.default({}),
|
||||||
|
gerbil: z
|
||||||
|
.object({
|
||||||
|
start_port: portSchema
|
||||||
|
.optional()
|
||||||
|
.default(51820)
|
||||||
|
.transform(stoi)
|
||||||
|
.pipe(portSchema),
|
||||||
base_endpoint: z
|
base_endpoint: z
|
||||||
.string()
|
.string()
|
||||||
.optional()
|
.optional()
|
||||||
.pipe(z.string())
|
.pipe(z.string())
|
||||||
.transform((url) => url.toLowerCase()),
|
.transform((url) => url.toLowerCase()),
|
||||||
use_subdomain: z.boolean(),
|
use_subdomain: z.boolean().optional().default(false),
|
||||||
subnet_group: z.string(),
|
subnet_group: z.string().optional().default("100.89.137.0/20"),
|
||||||
block_size: z.number().positive().gt(0),
|
block_size: z.number().positive().gt(0).optional().default(24),
|
||||||
site_block_size: z.number().positive().gt(0)
|
site_block_size: z.number().positive().gt(0).optional().default(30)
|
||||||
}),
|
})
|
||||||
rate_limits: z.object({
|
.optional()
|
||||||
global: z.object({
|
.default({}),
|
||||||
window_minutes: z.number().positive().gt(0),
|
rate_limits: z
|
||||||
max_requests: z.number().positive().gt(0)
|
.object({
|
||||||
}),
|
global: z
|
||||||
|
.object({
|
||||||
|
window_minutes: z
|
||||||
|
.number()
|
||||||
|
.positive()
|
||||||
|
.gt(0)
|
||||||
|
.optional()
|
||||||
|
.default(1),
|
||||||
|
max_requests: z
|
||||||
|
.number()
|
||||||
|
.positive()
|
||||||
|
.gt(0)
|
||||||
|
.optional()
|
||||||
|
.default(500)
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
.default({}),
|
||||||
auth: z
|
auth: z
|
||||||
.object({
|
.object({
|
||||||
window_minutes: z.number().positive().gt(0),
|
window_minutes: z.number().positive().gt(0),
|
||||||
max_requests: z.number().positive().gt(0)
|
max_requests: z.number().positive().gt(0)
|
||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
}),
|
})
|
||||||
|
.optional()
|
||||||
|
.default({}),
|
||||||
email: z
|
email: z
|
||||||
.object({
|
.object({
|
||||||
smtp_host: z.string().optional(),
|
smtp_host: z.string().optional(),
|
||||||
|
@ -164,7 +216,8 @@ const configSchema = z.object({
|
||||||
disable_user_create_org: z.boolean().optional(),
|
disable_user_create_org: z.boolean().optional(),
|
||||||
allow_raw_resources: z.boolean().optional(),
|
allow_raw_resources: z.boolean().optional(),
|
||||||
allow_base_domain_resources: z.boolean().optional(),
|
allow_base_domain_resources: z.boolean().optional(),
|
||||||
allow_local_sites: z.boolean().optional()
|
allow_local_sites: z.boolean().optional(),
|
||||||
|
enable_integration_api: z.boolean().optional()
|
||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,7 +2,7 @@ import path from "path";
|
||||||
import { fileURLToPath } from "url";
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
// This is a placeholder value replaced by the build process
|
// This is a placeholder value replaced by the build process
|
||||||
export const APP_VERSION = "1.3.0";
|
export const APP_VERSION = "1.4.0";
|
||||||
|
|
||||||
export const __FILENAME = fileURLToPath(import.meta.url);
|
export const __FILENAME = fileURLToPath(import.meta.url);
|
||||||
export const __DIRNAME = path.dirname(__FILENAME);
|
export const __DIRNAME = path.dirname(__FILENAME);
|
||||||
|
|
|
@ -9,6 +9,10 @@ export function isValidIP(ip: string): boolean {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isValidUrlGlobPattern(pattern: string): boolean {
|
export function isValidUrlGlobPattern(pattern: string): boolean {
|
||||||
|
if (pattern === "/") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Remove leading slash if present
|
// Remove leading slash if present
|
||||||
pattern = pattern.startsWith("/") ? pattern.slice(1) : pattern;
|
pattern = pattern.startsWith("/") ? pattern.slice(1) : pattern;
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import db from "@server/db";
|
import db from "@server/db";
|
||||||
import { hostMeta, licenseKey, sites } from "@server/db/schemas";
|
import { hostMeta, licenseKey, sites } from "@server/db/schemas";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import * as crypto from "crypto";
|
import * as crypto from "crypto";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
export * from "./verifyApiKey";
|
export * from "./verifyApiKey";
|
||||||
export * from "./verifyApiKeyOrgAccess";
|
export * from "./verifyApiKeyOrgAccess";
|
||||||
export * from "./verifyApiKeyHasAction";
|
export * from "./verifyApiKeyHasAction";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
import { resourceAccessToken, resources, apiKeyOrg } from "@server/db/schemas";
|
import { resourceAccessToken, resources, apiKeyOrg } from "@server/db/schemas";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { verifyPassword } from "@server/auth/password";
|
import { verifyPassword } from "@server/auth/password";
|
||||||
import db from "@server/db";
|
import db from "@server/db";
|
||||||
import { apiKeys } from "@server/db/schemas";
|
import { apiKeys } from "@server/db/schemas";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
import { apiKeys, apiKeyOrg } from "@server/db/schemas";
|
import { apiKeys, apiKeyOrg } from "@server/db/schemas";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
import { apiKeyOrg } from "@server/db/schemas";
|
import { apiKeyOrg } from "@server/db/schemas";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
import { resources, apiKeyOrg } from "@server/db/schemas";
|
import { resources, apiKeyOrg } from "@server/db/schemas";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
import { roles, apiKeyOrg } from "@server/db/schemas";
|
import { roles, apiKeyOrg } from "@server/db/schemas";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
import { userOrgs } from "@server/db/schemas";
|
import { userOrgs } from "@server/db/schemas";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
import {
|
import {
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
import { resources, targets, apiKeyOrg } from "@server/db/schemas";
|
import { resources, targets, apiKeyOrg } from "@server/db/schemas";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
import { userOrgs } from "@server/db/schemas";
|
import { userOrgs } from "@server/db/schemas";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
import { userOrgs, apiKeys, apiKeyOrg } from "@server/db/schemas";
|
import { userOrgs, apiKeys, apiKeyOrg } from "@server/db/schemas";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { NextFunction, Request, Response } from "express";
|
import { NextFunction, Request, Response } from "express";
|
||||||
import db from "@server/db";
|
import db from "@server/db";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { NextFunction, Request, Response } from "express";
|
import { NextFunction, Request, Response } from "express";
|
||||||
import db from "@server/db";
|
import db from "@server/db";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
export * from "./createRootApiKey";
|
export * from "./createRootApiKey";
|
||||||
export * from "./deleteApiKey";
|
export * from "./deleteApiKey";
|
||||||
export * from "./getApiKey";
|
export * from "./getApiKey";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
import { actions, apiKeyActions, apiKeyOrg, apiKeys } from "@server/db/schemas";
|
import { actions, apiKeyActions, apiKeyOrg, apiKeys } from "@server/db/schemas";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
import { apiKeyOrg, apiKeys } from "@server/db/schemas";
|
import { apiKeyOrg, apiKeys } from "@server/db/schemas";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
import { apiKeys } from "@server/db/schemas";
|
import { apiKeys } from "@server/db/schemas";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
|
|
|
@ -1,6 +1,78 @@
|
||||||
import { isPathAllowed } from './verifySession';
|
|
||||||
import { assertEquals } from '@test/assert';
|
import { assertEquals } from '@test/assert';
|
||||||
|
|
||||||
|
function isPathAllowed(pattern: string, path: string): boolean {
|
||||||
|
|
||||||
|
// Normalize and split paths into segments
|
||||||
|
const normalize = (p: string) => p.split("/").filter(Boolean);
|
||||||
|
const patternParts = normalize(pattern);
|
||||||
|
const pathParts = normalize(path);
|
||||||
|
|
||||||
|
|
||||||
|
// Recursive function to try different wildcard matches
|
||||||
|
function matchSegments(patternIndex: number, pathIndex: number): boolean {
|
||||||
|
const indent = " ".repeat(pathIndex); // Indent based on recursion depth
|
||||||
|
const currentPatternPart = patternParts[patternIndex];
|
||||||
|
const currentPathPart = pathParts[pathIndex];
|
||||||
|
|
||||||
|
// If we've consumed all pattern parts, we should have consumed all path parts
|
||||||
|
if (patternIndex >= patternParts.length) {
|
||||||
|
const result = pathIndex >= pathParts.length;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we've consumed all path parts but still have pattern parts
|
||||||
|
if (pathIndex >= pathParts.length) {
|
||||||
|
// The only way this can match is if all remaining pattern parts are wildcards
|
||||||
|
const remainingPattern = patternParts.slice(patternIndex);
|
||||||
|
const result = remainingPattern.every((p) => p === "*");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For full segment wildcards, try consuming different numbers of path segments
|
||||||
|
if (currentPatternPart === "*") {
|
||||||
|
|
||||||
|
// Try consuming 0 segments (skip the wildcard)
|
||||||
|
if (matchSegments(patternIndex + 1, pathIndex)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try consuming current segment and recursively try rest
|
||||||
|
if (matchSegments(patternIndex, pathIndex + 1)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for in-segment wildcard (e.g., "prefix*" or "prefix*suffix")
|
||||||
|
if (currentPatternPart.includes("*")) {
|
||||||
|
// Convert the pattern segment to a regex pattern
|
||||||
|
const regexPattern = currentPatternPart
|
||||||
|
.replace(/\*/g, ".*") // Replace * with .* for regex wildcard
|
||||||
|
.replace(/\?/g, "."); // Replace ? with . for single character wildcard if needed
|
||||||
|
|
||||||
|
const regex = new RegExp(`^${regexPattern}$`);
|
||||||
|
|
||||||
|
if (regex.test(currentPathPart)) {
|
||||||
|
return matchSegments(patternIndex + 1, pathIndex + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For regular segments, they must match exactly
|
||||||
|
if (currentPatternPart !== currentPathPart) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to next segments in both pattern and path
|
||||||
|
return matchSegments(patternIndex + 1, pathIndex + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = matchSegments(0, 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
function runTests() {
|
function runTests() {
|
||||||
console.log('Running path matching tests...');
|
console.log('Running path matching tests...');
|
||||||
|
|
||||||
|
@ -56,6 +128,9 @@ function runTests() {
|
||||||
assertEquals(isPathAllowed('test*', 'testuser'), true, 'Asterisk as part of segment name is treated as a literal, not a wildcard');
|
assertEquals(isPathAllowed('test*', 'testuser'), true, 'Asterisk as part of segment name is treated as a literal, not a wildcard');
|
||||||
assertEquals(isPathAllowed('my*app', 'myapp'), true, 'Asterisk in middle of segment name is treated as a literal, not a wildcard');
|
assertEquals(isPathAllowed('my*app', 'myapp'), true, 'Asterisk in middle of segment name is treated as a literal, not a wildcard');
|
||||||
|
|
||||||
|
assertEquals(isPathAllowed('/', '/'), true, 'Root path should match root path');
|
||||||
|
assertEquals(isPathAllowed('/', '/test'), false, 'Root path should not match non-root path');
|
||||||
|
|
||||||
console.log('All tests passed!');
|
console.log('All tests passed!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,6 @@ import {
|
||||||
verifyUserIsServerAdmin,
|
verifyUserIsServerAdmin,
|
||||||
verifyIsLoggedInUser,
|
verifyIsLoggedInUser,
|
||||||
verifyApiKeyAccess,
|
verifyApiKeyAccess,
|
||||||
verifyValidLicense
|
|
||||||
} from "@server/middlewares";
|
} from "@server/middlewares";
|
||||||
import { verifyUserHasAction } from "../middlewares/verifyUserHasAction";
|
import { verifyUserHasAction } from "../middlewares/verifyUserHasAction";
|
||||||
import { ActionsEnum } from "@server/auth/actions";
|
import { ActionsEnum } from "@server/auth/actions";
|
||||||
|
@ -531,28 +530,24 @@ authenticated.get("/idp/:idpId", verifyUserIsServerAdmin, idp.getIdp);
|
||||||
|
|
||||||
authenticated.put(
|
authenticated.put(
|
||||||
"/idp/:idpId/org/:orgId",
|
"/idp/:idpId/org/:orgId",
|
||||||
verifyValidLicense,
|
|
||||||
verifyUserIsServerAdmin,
|
verifyUserIsServerAdmin,
|
||||||
idp.createIdpOrgPolicy
|
idp.createIdpOrgPolicy
|
||||||
);
|
);
|
||||||
|
|
||||||
authenticated.post(
|
authenticated.post(
|
||||||
"/idp/:idpId/org/:orgId",
|
"/idp/:idpId/org/:orgId",
|
||||||
verifyValidLicense,
|
|
||||||
verifyUserIsServerAdmin,
|
verifyUserIsServerAdmin,
|
||||||
idp.updateIdpOrgPolicy
|
idp.updateIdpOrgPolicy
|
||||||
);
|
);
|
||||||
|
|
||||||
authenticated.delete(
|
authenticated.delete(
|
||||||
"/idp/:idpId/org/:orgId",
|
"/idp/:idpId/org/:orgId",
|
||||||
verifyValidLicense,
|
|
||||||
verifyUserIsServerAdmin,
|
verifyUserIsServerAdmin,
|
||||||
idp.deleteIdpOrgPolicy
|
idp.deleteIdpOrgPolicy
|
||||||
);
|
);
|
||||||
|
|
||||||
authenticated.get(
|
authenticated.get(
|
||||||
"/idp/:idpId/org",
|
"/idp/:idpId/org",
|
||||||
verifyValidLicense,
|
|
||||||
verifyUserIsServerAdmin,
|
verifyUserIsServerAdmin,
|
||||||
idp.listIdpOrgPolicies
|
idp.listIdpOrgPolicies
|
||||||
);
|
);
|
||||||
|
@ -586,49 +581,42 @@ authenticated.post(
|
||||||
|
|
||||||
authenticated.get(
|
authenticated.get(
|
||||||
`/api-key/:apiKeyId`,
|
`/api-key/:apiKeyId`,
|
||||||
verifyValidLicense,
|
|
||||||
verifyUserIsServerAdmin,
|
verifyUserIsServerAdmin,
|
||||||
apiKeys.getApiKey
|
apiKeys.getApiKey
|
||||||
);
|
);
|
||||||
|
|
||||||
authenticated.put(
|
authenticated.put(
|
||||||
`/api-key`,
|
`/api-key`,
|
||||||
verifyValidLicense,
|
|
||||||
verifyUserIsServerAdmin,
|
verifyUserIsServerAdmin,
|
||||||
apiKeys.createRootApiKey
|
apiKeys.createRootApiKey
|
||||||
);
|
);
|
||||||
|
|
||||||
authenticated.delete(
|
authenticated.delete(
|
||||||
`/api-key/:apiKeyId`,
|
`/api-key/:apiKeyId`,
|
||||||
verifyValidLicense,
|
|
||||||
verifyUserIsServerAdmin,
|
verifyUserIsServerAdmin,
|
||||||
apiKeys.deleteApiKey
|
apiKeys.deleteApiKey
|
||||||
);
|
);
|
||||||
|
|
||||||
authenticated.get(
|
authenticated.get(
|
||||||
`/api-keys`,
|
`/api-keys`,
|
||||||
verifyValidLicense,
|
|
||||||
verifyUserIsServerAdmin,
|
verifyUserIsServerAdmin,
|
||||||
apiKeys.listRootApiKeys
|
apiKeys.listRootApiKeys
|
||||||
);
|
);
|
||||||
|
|
||||||
authenticated.get(
|
authenticated.get(
|
||||||
`/api-key/:apiKeyId/actions`,
|
`/api-key/:apiKeyId/actions`,
|
||||||
verifyValidLicense,
|
|
||||||
verifyUserIsServerAdmin,
|
verifyUserIsServerAdmin,
|
||||||
apiKeys.listApiKeyActions
|
apiKeys.listApiKeyActions
|
||||||
);
|
);
|
||||||
|
|
||||||
authenticated.post(
|
authenticated.post(
|
||||||
`/api-key/:apiKeyId/actions`,
|
`/api-key/:apiKeyId/actions`,
|
||||||
verifyValidLicense,
|
|
||||||
verifyUserIsServerAdmin,
|
verifyUserIsServerAdmin,
|
||||||
apiKeys.setApiKeyActions
|
apiKeys.setApiKeyActions
|
||||||
);
|
);
|
||||||
|
|
||||||
authenticated.get(
|
authenticated.get(
|
||||||
`/org/:orgId/api-keys`,
|
`/org/:orgId/api-keys`,
|
||||||
verifyValidLicense,
|
|
||||||
verifyOrgAccess,
|
verifyOrgAccess,
|
||||||
verifyUserHasAction(ActionsEnum.listApiKeys),
|
verifyUserHasAction(ActionsEnum.listApiKeys),
|
||||||
apiKeys.listOrgApiKeys
|
apiKeys.listOrgApiKeys
|
||||||
|
@ -636,7 +624,6 @@ authenticated.get(
|
||||||
|
|
||||||
authenticated.post(
|
authenticated.post(
|
||||||
`/org/:orgId/api-key/:apiKeyId/actions`,
|
`/org/:orgId/api-key/:apiKeyId/actions`,
|
||||||
verifyValidLicense,
|
|
||||||
verifyOrgAccess,
|
verifyOrgAccess,
|
||||||
verifyApiKeyAccess,
|
verifyApiKeyAccess,
|
||||||
verifyUserHasAction(ActionsEnum.setApiKeyActions),
|
verifyUserHasAction(ActionsEnum.setApiKeyActions),
|
||||||
|
@ -645,7 +632,6 @@ authenticated.post(
|
||||||
|
|
||||||
authenticated.get(
|
authenticated.get(
|
||||||
`/org/:orgId/api-key/:apiKeyId/actions`,
|
`/org/:orgId/api-key/:apiKeyId/actions`,
|
||||||
verifyValidLicense,
|
|
||||||
verifyOrgAccess,
|
verifyOrgAccess,
|
||||||
verifyApiKeyAccess,
|
verifyApiKeyAccess,
|
||||||
verifyUserHasAction(ActionsEnum.listApiKeyActions),
|
verifyUserHasAction(ActionsEnum.listApiKeyActions),
|
||||||
|
@ -654,7 +640,6 @@ authenticated.get(
|
||||||
|
|
||||||
authenticated.put(
|
authenticated.put(
|
||||||
`/org/:orgId/api-key`,
|
`/org/:orgId/api-key`,
|
||||||
verifyValidLicense,
|
|
||||||
verifyOrgAccess,
|
verifyOrgAccess,
|
||||||
verifyUserHasAction(ActionsEnum.createApiKey),
|
verifyUserHasAction(ActionsEnum.createApiKey),
|
||||||
apiKeys.createOrgApiKey
|
apiKeys.createOrgApiKey
|
||||||
|
@ -662,7 +647,6 @@ authenticated.put(
|
||||||
|
|
||||||
authenticated.delete(
|
authenticated.delete(
|
||||||
`/org/:orgId/api-key/:apiKeyId`,
|
`/org/:orgId/api-key/:apiKeyId`,
|
||||||
verifyValidLicense,
|
|
||||||
verifyOrgAccess,
|
verifyOrgAccess,
|
||||||
verifyApiKeyAccess,
|
verifyApiKeyAccess,
|
||||||
verifyUserHasAction(ActionsEnum.deleteApiKey),
|
verifyUserHasAction(ActionsEnum.deleteApiKey),
|
||||||
|
@ -671,7 +655,6 @@ authenticated.delete(
|
||||||
|
|
||||||
authenticated.get(
|
authenticated.get(
|
||||||
`/org/:orgId/api-key/:apiKeyId`,
|
`/org/:orgId/api-key/:apiKeyId`,
|
||||||
verifyValidLicense,
|
|
||||||
verifyOrgAccess,
|
verifyOrgAccess,
|
||||||
verifyApiKeyAccess,
|
verifyApiKeyAccess,
|
||||||
verifyUserHasAction(ActionsEnum.getApiKey),
|
verifyUserHasAction(ActionsEnum.getApiKey),
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
|
|
|
@ -81,10 +81,6 @@ export async function createOidcIdp(
|
||||||
autoProvision
|
autoProvision
|
||||||
} = parsedBody.data;
|
} = parsedBody.data;
|
||||||
|
|
||||||
if (!(await license.isUnlocked())) {
|
|
||||||
autoProvision = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const key = config.getRawConfig().server.secret;
|
const key = config.getRawConfig().server.secret;
|
||||||
|
|
||||||
const encryptedSecret = encrypt(clientSecret, key);
|
const encryptedSecret = encrypt(clientSecret, key);
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
|
|
|
@ -1,233 +0,0 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import {
|
|
||||||
createSession,
|
|
||||||
generateId,
|
|
||||||
generateSessionToken,
|
|
||||||
serializeSessionCookie
|
|
||||||
} from "@server/auth/sessions/app";
|
|
||||||
import db from "@server/db";
|
|
||||||
import { Idp, idpOrg, orgs, roles, User, userOrgs, users } from "@server/db/schemas";
|
|
||||||
import logger from "@server/logger";
|
|
||||||
import { UserType } from "@server/types/UserTypes";
|
|
||||||
import { eq, and, inArray } from "drizzle-orm";
|
|
||||||
import jmespath from "jmespath";
|
|
||||||
import { Request, Response } from "express";
|
|
||||||
|
|
||||||
export async function oidcAutoProvision({
|
|
||||||
idp,
|
|
||||||
claims,
|
|
||||||
existingUser,
|
|
||||||
userIdentifier,
|
|
||||||
email,
|
|
||||||
name,
|
|
||||||
req,
|
|
||||||
res
|
|
||||||
}: {
|
|
||||||
idp: Idp;
|
|
||||||
claims: any;
|
|
||||||
existingUser?: User;
|
|
||||||
userIdentifier: string;
|
|
||||||
email?: string;
|
|
||||||
name?: string;
|
|
||||||
req: Request;
|
|
||||||
res: Response;
|
|
||||||
}) {
|
|
||||||
const allOrgs = await db.select().from(orgs);
|
|
||||||
|
|
||||||
const defaultRoleMapping = idp.defaultRoleMapping;
|
|
||||||
const defaultOrgMapping = idp.defaultOrgMapping;
|
|
||||||
|
|
||||||
let userOrgInfo: { orgId: string; roleId: number }[] = [];
|
|
||||||
for (const org of allOrgs) {
|
|
||||||
const [idpOrgRes] = await db
|
|
||||||
.select()
|
|
||||||
.from(idpOrg)
|
|
||||||
.where(
|
|
||||||
and(eq(idpOrg.idpId, idp.idpId), eq(idpOrg.orgId, org.orgId))
|
|
||||||
);
|
|
||||||
|
|
||||||
let roleId: number | undefined = undefined;
|
|
||||||
|
|
||||||
const orgMapping = idpOrgRes?.orgMapping || defaultOrgMapping;
|
|
||||||
const hydratedOrgMapping = hydrateOrgMapping(orgMapping, org.orgId);
|
|
||||||
|
|
||||||
if (hydratedOrgMapping) {
|
|
||||||
logger.debug("Hydrated Org Mapping", {
|
|
||||||
hydratedOrgMapping
|
|
||||||
});
|
|
||||||
const orgId = jmespath.search(claims, hydratedOrgMapping);
|
|
||||||
logger.debug("Extraced Org ID", { orgId });
|
|
||||||
if (orgId !== true && orgId !== org.orgId) {
|
|
||||||
// user not allowed to access this org
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const roleMapping = idpOrgRes?.roleMapping || defaultRoleMapping;
|
|
||||||
if (roleMapping) {
|
|
||||||
logger.debug("Role Mapping", { roleMapping });
|
|
||||||
const roleName = jmespath.search(claims, roleMapping);
|
|
||||||
|
|
||||||
if (!roleName) {
|
|
||||||
logger.error("Role name not found in the ID token", {
|
|
||||||
roleName
|
|
||||||
});
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [roleRes] = await db
|
|
||||||
.select()
|
|
||||||
.from(roles)
|
|
||||||
.where(
|
|
||||||
and(eq(roles.orgId, org.orgId), eq(roles.name, roleName))
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!roleRes) {
|
|
||||||
logger.error("Role not found", {
|
|
||||||
orgId: org.orgId,
|
|
||||||
roleName
|
|
||||||
});
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
roleId = roleRes.roleId;
|
|
||||||
|
|
||||||
userOrgInfo.push({
|
|
||||||
orgId: org.orgId,
|
|
||||||
roleId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug("User org info", { userOrgInfo });
|
|
||||||
|
|
||||||
let existingUserId = existingUser?.userId;
|
|
||||||
|
|
||||||
// sync the user with the orgs and roles
|
|
||||||
await db.transaction(async (trx) => {
|
|
||||||
let userId = existingUser?.userId;
|
|
||||||
|
|
||||||
// create user if not exists
|
|
||||||
if (!existingUser) {
|
|
||||||
userId = generateId(15);
|
|
||||||
|
|
||||||
await trx.insert(users).values({
|
|
||||||
userId,
|
|
||||||
username: userIdentifier,
|
|
||||||
email: email || null,
|
|
||||||
name: name || null,
|
|
||||||
type: UserType.OIDC,
|
|
||||||
idpId: idp.idpId,
|
|
||||||
emailVerified: true, // OIDC users are always verified
|
|
||||||
dateCreated: new Date().toISOString()
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// set the name and email
|
|
||||||
await trx
|
|
||||||
.update(users)
|
|
||||||
.set({
|
|
||||||
username: userIdentifier,
|
|
||||||
email: email || null,
|
|
||||||
name: name || null
|
|
||||||
})
|
|
||||||
.where(eq(users.userId, userId!));
|
|
||||||
}
|
|
||||||
|
|
||||||
existingUserId = userId;
|
|
||||||
|
|
||||||
// get all current user orgs
|
|
||||||
const currentUserOrgs = await trx
|
|
||||||
.select()
|
|
||||||
.from(userOrgs)
|
|
||||||
.where(eq(userOrgs.userId, userId!));
|
|
||||||
|
|
||||||
// Delete orgs that are no longer valid
|
|
||||||
const orgsToDelete = currentUserOrgs.filter(
|
|
||||||
(currentOrg) =>
|
|
||||||
!userOrgInfo.some((newOrg) => newOrg.orgId === currentOrg.orgId)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (orgsToDelete.length > 0) {
|
|
||||||
await trx.delete(userOrgs).where(
|
|
||||||
and(
|
|
||||||
eq(userOrgs.userId, userId!),
|
|
||||||
inArray(
|
|
||||||
userOrgs.orgId,
|
|
||||||
orgsToDelete.map((org) => org.orgId)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update roles for existing orgs where the role has changed
|
|
||||||
const orgsToUpdate = currentUserOrgs.filter((currentOrg) => {
|
|
||||||
const newOrg = userOrgInfo.find(
|
|
||||||
(newOrg) => newOrg.orgId === currentOrg.orgId
|
|
||||||
);
|
|
||||||
return newOrg && newOrg.roleId !== currentOrg.roleId;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (orgsToUpdate.length > 0) {
|
|
||||||
for (const org of orgsToUpdate) {
|
|
||||||
const newRole = userOrgInfo.find(
|
|
||||||
(newOrg) => newOrg.orgId === org.orgId
|
|
||||||
);
|
|
||||||
if (newRole) {
|
|
||||||
await trx
|
|
||||||
.update(userOrgs)
|
|
||||||
.set({ roleId: newRole.roleId })
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(userOrgs.userId, userId!),
|
|
||||||
eq(userOrgs.orgId, org.orgId)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add new orgs that don't exist yet
|
|
||||||
const orgsToAdd = userOrgInfo.filter(
|
|
||||||
(newOrg) =>
|
|
||||||
!currentUserOrgs.some(
|
|
||||||
(currentOrg) => currentOrg.orgId === newOrg.orgId
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (orgsToAdd.length > 0) {
|
|
||||||
await trx.insert(userOrgs).values(
|
|
||||||
orgsToAdd.map((org) => ({
|
|
||||||
userId: userId!,
|
|
||||||
orgId: org.orgId,
|
|
||||||
roleId: org.roleId,
|
|
||||||
dateCreated: new Date().toISOString()
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const token = generateSessionToken();
|
|
||||||
const sess = await createSession(token, existingUserId!);
|
|
||||||
const isSecure = req.protocol === "https";
|
|
||||||
const cookie = serializeSessionCookie(
|
|
||||||
token,
|
|
||||||
isSecure,
|
|
||||||
new Date(sess.expiresAt)
|
|
||||||
);
|
|
||||||
|
|
||||||
res.appendHeader("Set-Cookie", cookie);
|
|
||||||
}
|
|
||||||
|
|
||||||
function hydrateOrgMapping(
|
|
||||||
orgMapping: string | null,
|
|
||||||
orgId: string
|
|
||||||
): string | undefined {
|
|
||||||
if (!orgMapping) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return orgMapping.split("{{orgId}}").join(orgId);
|
|
||||||
}
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
|
|
|
@ -100,10 +100,6 @@ export async function updateOidcIdp(
|
||||||
defaultOrgMapping
|
defaultOrgMapping
|
||||||
} = parsedBody.data;
|
} = parsedBody.data;
|
||||||
|
|
||||||
if (!(await license.isUnlocked())) {
|
|
||||||
autoProvision = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if IDP exists and is of type OIDC
|
// Check if IDP exists and is of type OIDC
|
||||||
const [existingIdp] = await db
|
const [existingIdp] = await db
|
||||||
.select()
|
.select()
|
||||||
|
|
|
@ -6,7 +6,15 @@ import HttpCode from "@server/types/HttpCode";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { idp, idpOidcConfig, users } from "@server/db/schemas";
|
import {
|
||||||
|
idp,
|
||||||
|
idpOidcConfig,
|
||||||
|
idpOrg,
|
||||||
|
orgs,
|
||||||
|
roles,
|
||||||
|
userOrgs,
|
||||||
|
users
|
||||||
|
} from "@server/db/schemas";
|
||||||
import { and, eq, inArray } from "drizzle-orm";
|
import { and, eq, inArray } from "drizzle-orm";
|
||||||
import * as arctic from "arctic";
|
import * as arctic from "arctic";
|
||||||
import { generateOidcRedirectUrl } from "@server/lib/idp/generateRedirectUrl";
|
import { generateOidcRedirectUrl } from "@server/lib/idp/generateRedirectUrl";
|
||||||
|
@ -15,12 +23,12 @@ import jsonwebtoken from "jsonwebtoken";
|
||||||
import config from "@server/lib/config";
|
import config from "@server/lib/config";
|
||||||
import {
|
import {
|
||||||
createSession,
|
createSession,
|
||||||
|
generateId,
|
||||||
generateSessionToken,
|
generateSessionToken,
|
||||||
serializeSessionCookie
|
serializeSessionCookie
|
||||||
} from "@server/auth/sessions/app";
|
} from "@server/auth/sessions/app";
|
||||||
import { decrypt } from "@server/lib/crypto";
|
import { decrypt } from "@server/lib/crypto";
|
||||||
import { oidcAutoProvision } from "./oidcAutoProvision";
|
import { UserType } from "@server/types/UserTypes";
|
||||||
import license from "@server/license/license";
|
|
||||||
|
|
||||||
const ensureTrailingSlash = (url: string): string => {
|
const ensureTrailingSlash = (url: string): string => {
|
||||||
return url;
|
return url;
|
||||||
|
@ -160,7 +168,9 @@ export async function validateOidcCallback(
|
||||||
);
|
);
|
||||||
|
|
||||||
const idToken = tokens.idToken();
|
const idToken = tokens.idToken();
|
||||||
|
logger.debug("ID token", { idToken });
|
||||||
const claims = arctic.decodeIdToken(idToken);
|
const claims = arctic.decodeIdToken(idToken);
|
||||||
|
logger.debug("ID token claims", { claims });
|
||||||
|
|
||||||
const userIdentifier = jmespath.search(
|
const userIdentifier = jmespath.search(
|
||||||
claims,
|
claims,
|
||||||
|
@ -210,25 +220,203 @@ export async function validateOidcCallback(
|
||||||
);
|
);
|
||||||
|
|
||||||
if (existingIdp.idp.autoProvision) {
|
if (existingIdp.idp.autoProvision) {
|
||||||
if (!(await license.isUnlocked())) {
|
const allOrgs = await db.select().from(orgs);
|
||||||
return next(
|
|
||||||
createHttpError(
|
const defaultRoleMapping = existingIdp.idp.defaultRoleMapping;
|
||||||
HttpCode.FORBIDDEN,
|
const defaultOrgMapping = existingIdp.idp.defaultOrgMapping;
|
||||||
"Auto-provisioning is not available"
|
|
||||||
|
let userOrgInfo: { orgId: string; roleId: number }[] = [];
|
||||||
|
for (const org of allOrgs) {
|
||||||
|
const [idpOrgRes] = await db
|
||||||
|
.select()
|
||||||
|
.from(idpOrg)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(idpOrg.idpId, existingIdp.idp.idpId),
|
||||||
|
eq(idpOrg.orgId, org.orgId)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
let roleId: number | undefined = undefined;
|
||||||
|
|
||||||
|
const orgMapping = idpOrgRes?.orgMapping || defaultOrgMapping;
|
||||||
|
const hydratedOrgMapping = hydrateOrgMapping(
|
||||||
|
orgMapping,
|
||||||
|
org.orgId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (hydratedOrgMapping) {
|
||||||
|
logger.debug("Hydrated Org Mapping", {
|
||||||
|
hydratedOrgMapping
|
||||||
|
});
|
||||||
|
const orgId = jmespath.search(claims, hydratedOrgMapping);
|
||||||
|
logger.debug("Extraced Org ID", { orgId });
|
||||||
|
if (orgId !== true && orgId !== org.orgId) {
|
||||||
|
// user not allowed to access this org
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const roleMapping =
|
||||||
|
idpOrgRes?.roleMapping || defaultRoleMapping;
|
||||||
|
if (roleMapping) {
|
||||||
|
logger.debug("Role Mapping", { roleMapping });
|
||||||
|
const roleName = jmespath.search(claims, roleMapping);
|
||||||
|
|
||||||
|
if (!roleName) {
|
||||||
|
logger.error("Role name not found in the ID token", {
|
||||||
|
roleName
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [roleRes] = await db
|
||||||
|
.select()
|
||||||
|
.from(roles)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(roles.orgId, org.orgId),
|
||||||
|
eq(roles.name, roleName)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!roleRes) {
|
||||||
|
logger.error("Role not found", {
|
||||||
|
orgId: org.orgId,
|
||||||
|
roleName
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
roleId = roleRes.roleId;
|
||||||
|
|
||||||
|
userOrgInfo.push({
|
||||||
|
orgId: org.orgId,
|
||||||
|
roleId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug("User org info", { userOrgInfo });
|
||||||
|
|
||||||
|
let existingUserId = existingUser?.userId;
|
||||||
|
|
||||||
|
// sync the user with the orgs and roles
|
||||||
|
await db.transaction(async (trx) => {
|
||||||
|
let userId = existingUser?.userId;
|
||||||
|
|
||||||
|
// create user if not exists
|
||||||
|
if (!existingUser) {
|
||||||
|
userId = generateId(15);
|
||||||
|
|
||||||
|
await trx.insert(users).values({
|
||||||
|
userId,
|
||||||
|
username: userIdentifier,
|
||||||
|
email: email || null,
|
||||||
|
name: name || null,
|
||||||
|
type: UserType.OIDC,
|
||||||
|
idpId: existingIdp.idp.idpId,
|
||||||
|
emailVerified: true, // OIDC users are always verified
|
||||||
|
dateCreated: new Date().toISOString()
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// set the name and email
|
||||||
|
await trx
|
||||||
|
.update(users)
|
||||||
|
.set({
|
||||||
|
username: userIdentifier,
|
||||||
|
email: email || null,
|
||||||
|
name: name || null
|
||||||
|
})
|
||||||
|
.where(eq(users.userId, userId!));
|
||||||
|
}
|
||||||
|
|
||||||
|
existingUserId = userId;
|
||||||
|
|
||||||
|
// get all current user orgs
|
||||||
|
const currentUserOrgs = await trx
|
||||||
|
.select()
|
||||||
|
.from(userOrgs)
|
||||||
|
.where(eq(userOrgs.userId, userId!));
|
||||||
|
|
||||||
|
// Delete orgs that are no longer valid
|
||||||
|
const orgsToDelete = currentUserOrgs.filter(
|
||||||
|
(currentOrg) =>
|
||||||
|
!userOrgInfo.some(
|
||||||
|
(newOrg) => newOrg.orgId === currentOrg.orgId
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (orgsToDelete.length > 0) {
|
||||||
|
await trx.delete(userOrgs).where(
|
||||||
|
and(
|
||||||
|
eq(userOrgs.userId, userId!),
|
||||||
|
inArray(
|
||||||
|
userOrgs.orgId,
|
||||||
|
orgsToDelete.map((org) => org.orgId)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
await oidcAutoProvision({
|
|
||||||
idp: existingIdp.idp,
|
// Update roles for existing orgs where the role has changed
|
||||||
userIdentifier,
|
const orgsToUpdate = currentUserOrgs.filter((currentOrg) => {
|
||||||
email,
|
const newOrg = userOrgInfo.find(
|
||||||
name,
|
(newOrg) => newOrg.orgId === currentOrg.orgId
|
||||||
claims,
|
);
|
||||||
existingUser,
|
return newOrg && newOrg.roleId !== currentOrg.roleId;
|
||||||
req,
|
|
||||||
res
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (orgsToUpdate.length > 0) {
|
||||||
|
for (const org of orgsToUpdate) {
|
||||||
|
const newRole = userOrgInfo.find(
|
||||||
|
(newOrg) => newOrg.orgId === org.orgId
|
||||||
|
);
|
||||||
|
if (newRole) {
|
||||||
|
await trx
|
||||||
|
.update(userOrgs)
|
||||||
|
.set({ roleId: newRole.roleId })
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(userOrgs.userId, userId!),
|
||||||
|
eq(userOrgs.orgId, org.orgId)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new orgs that don't exist yet
|
||||||
|
const orgsToAdd = userOrgInfo.filter(
|
||||||
|
(newOrg) =>
|
||||||
|
!currentUserOrgs.some(
|
||||||
|
(currentOrg) => currentOrg.orgId === newOrg.orgId
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (orgsToAdd.length > 0) {
|
||||||
|
await trx.insert(userOrgs).values(
|
||||||
|
orgsToAdd.map((org) => ({
|
||||||
|
userId: userId!,
|
||||||
|
orgId: org.orgId,
|
||||||
|
roleId: org.roleId,
|
||||||
|
dateCreated: new Date().toISOString()
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const token = generateSessionToken();
|
||||||
|
const sess = await createSession(token, existingUserId!);
|
||||||
|
const isSecure = req.protocol === "https";
|
||||||
|
const cookie = serializeSessionCookie(
|
||||||
|
token,
|
||||||
|
isSecure,
|
||||||
|
new Date(sess.expiresAt)
|
||||||
|
);
|
||||||
|
|
||||||
|
res.appendHeader("Set-Cookie", cookie);
|
||||||
|
|
||||||
return response<ValidateOidcUrlCallbackResponse>(res, {
|
return response<ValidateOidcUrlCallbackResponse>(res, {
|
||||||
data: {
|
data: {
|
||||||
redirectUrl: postAuthRedirectUrl
|
redirectUrl: postAuthRedirectUrl
|
||||||
|
@ -276,3 +464,13 @@ export async function validateOidcCallback(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hydrateOrgMapping(
|
||||||
|
orgMapping: string | null,
|
||||||
|
orgId: string
|
||||||
|
): string | undefined {
|
||||||
|
if (!orgMapping) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return orgMapping.split("{{orgId}}").join(orgId);
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import * as site from "./site";
|
import * as site from "./site";
|
||||||
import * as org from "./org";
|
import * as org from "./org";
|
||||||
import * as resource from "./resource";
|
import * as resource from "./resource";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
export * from "./getLicenseStatus";
|
export * from "./getLicenseStatus";
|
||||||
export * from "./activateLicense";
|
export * from "./activateLicense";
|
||||||
export * from "./listLicenseKeys";
|
export * from "./listLicenseKeys";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
|
|
|
@ -318,8 +318,8 @@ async function updateHttpResource(
|
||||||
domainId: updatePayload.domainId,
|
domainId: updatePayload.domainId,
|
||||||
enabled: updatePayload.enabled,
|
enabled: updatePayload.enabled,
|
||||||
stickySession: updatePayload.stickySession,
|
stickySession: updatePayload.stickySession,
|
||||||
tlsServerName: updatePayload.tlsServerName || null,
|
tlsServerName: updatePayload.tlsServerName,
|
||||||
setHostHeader: updatePayload.setHostHeader || null,
|
setHostHeader: updatePayload.setHostHeader,
|
||||||
fullDomain: updatePayload.fullDomain
|
fullDomain: updatePayload.fullDomain
|
||||||
})
|
})
|
||||||
.where(eq(resources.resourceId, resource.resourceId))
|
.where(eq(resources.resourceId, resource.resourceId))
|
||||||
|
|
|
@ -42,7 +42,7 @@ import { createApiClient } from "@app/lib/api";
|
||||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
email: z.string().email({ message: "Please enter a valid email" }),
|
username: z.string(),
|
||||||
roleId: z.string().min(1, { message: "Please select a role" })
|
roleId: z.string().min(1, { message: "Please select a role" })
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ export default function AccessControlsPage() {
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
email: user.email!,
|
username: user.username!,
|
||||||
roleId: user.roleId?.toString()
|
roleId: user.roleId?.toString()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { DataTable } from "@app/components/ui/data-table";
|
import { DataTable } from "@app/components/ui/data-table";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { ColumnDef } from "@tanstack/react-table";
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { internal } from "@app/lib/api";
|
import { internal } from "@app/lib/api";
|
||||||
import { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
export default async function ApiKeysPage(props: {
|
export default async function ApiKeysPage(props: {
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import PermissionsSelectBox from "@app/components/PermissionsSelectBox";
|
import PermissionsSelectBox from "@app/components/PermissionsSelectBox";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { internal } from "@app/lib/api";
|
import { internal } from "@app/lib/api";
|
||||||
import { authCookieHeader } from "@app/lib/api/cookies";
|
import { authCookieHeader } from "@app/lib/api/cookies";
|
||||||
import { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
ColumnDef,
|
|
||||||
} from "@tanstack/react-table";
|
|
||||||
import { DataTable } from "@app/components/ui/data-table";
|
import { DataTable } from "@app/components/ui/data-table";
|
||||||
|
|
||||||
interface DataTableProps<TData, TValue> {
|
interface DataTableProps<TData, TValue> {
|
||||||
|
@ -25,6 +23,10 @@ export function ResourcesDataTable<TData, TValue>({
|
||||||
searchColumn="name"
|
searchColumn="name"
|
||||||
onAdd={createResource}
|
onAdd={createResource}
|
||||||
addButtonText="Add Resource"
|
addButtonText="Add Resource"
|
||||||
|
defaultSort={{
|
||||||
|
id: "name",
|
||||||
|
desc: false
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -320,8 +320,10 @@ export default function ReverseProxyTargets(props: {
|
||||||
AxiosResponse<CreateTargetResponse>
|
AxiosResponse<CreateTargetResponse>
|
||||||
>(`/resource/${params.resourceId}/target`, data);
|
>(`/resource/${params.resourceId}/target`, data);
|
||||||
target.targetId = res.data.data.targetId;
|
target.targetId = res.data.data.targetId;
|
||||||
|
target.new = false;
|
||||||
} else if (target.updated) {
|
} else if (target.updated) {
|
||||||
await api.post(`/target/${target.targetId}`, data);
|
await api.post(`/target/${target.targetId}`, data);
|
||||||
|
target.updated = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -796,6 +798,12 @@ export default function ReverseProxyTargets(props: {
|
||||||
type="submit"
|
type="submit"
|
||||||
variant="outlinePrimary"
|
variant="outlinePrimary"
|
||||||
className="mt-6"
|
className="mt-6"
|
||||||
|
disabled={
|
||||||
|
!(
|
||||||
|
addTargetForm.getValues("ip") &&
|
||||||
|
addTargetForm.getValues("port")
|
||||||
|
)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Add Target
|
Add Target
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -64,7 +64,6 @@ import {
|
||||||
InfoSections,
|
InfoSections,
|
||||||
InfoSectionTitle
|
InfoSectionTitle
|
||||||
} from "@app/components/InfoSection";
|
} from "@app/components/InfoSection";
|
||||||
import { Separator } from "@app/components/ui/separator";
|
|
||||||
import { InfoPopup } from "@app/components/ui/info-popup";
|
import { InfoPopup } from "@app/components/ui/info-popup";
|
||||||
import {
|
import {
|
||||||
isValidCIDR,
|
isValidCIDR,
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
ColumnDef,
|
|
||||||
} from "@tanstack/react-table";
|
|
||||||
import { DataTable } from "@app/components/ui/data-table";
|
import { DataTable } from "@app/components/ui/data-table";
|
||||||
|
|
||||||
interface DataTableProps<TData, TValue> {
|
interface DataTableProps<TData, TValue> {
|
||||||
|
@ -25,6 +23,10 @@ export function SitesDataTable<TData, TValue>({
|
||||||
searchColumn="name"
|
searchColumn="name"
|
||||||
onAdd={createSite}
|
onAdd={createSite}
|
||||||
addButtonText="Add Site"
|
addButtonText="Add Site"
|
||||||
|
defaultSort={{
|
||||||
|
id: "name",
|
||||||
|
desc: false
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { ColumnDef } from "@tanstack/react-table";
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { internal } from "@app/lib/api";
|
import { internal } from "@app/lib/api";
|
||||||
import { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
export default async function ApiKeysPage(props: {
|
export default async function ApiKeysPage(props: {
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import PermissionsSelectBox from "@app/components/PermissionsSelectBox";
|
import PermissionsSelectBox from "@app/components/PermissionsSelectBox";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { internal } from "@app/lib/api";
|
import { internal } from "@app/lib/api";
|
||||||
import { authCookieHeader } from "@app/lib/api/cookies";
|
import { authCookieHeader } from "@app/lib/api/cookies";
|
||||||
import { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
|
|
|
@ -232,7 +232,6 @@ export default function GeneralPage() {
|
||||||
defaultChecked={form.getValues(
|
defaultChecked={form.getValues(
|
||||||
"autoProvision"
|
"autoProvision"
|
||||||
)}
|
)}
|
||||||
disabled={!isUnlocked()}
|
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => {
|
||||||
form.setValue(
|
form.setValue(
|
||||||
"autoProvision",
|
"autoProvision",
|
||||||
|
@ -240,14 +239,6 @@ export default function GeneralPage() {
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{!isUnlocked() && (
|
|
||||||
<Badge
|
|
||||||
variant="outlinePrimary"
|
|
||||||
className="ml-2"
|
|
||||||
>
|
|
||||||
Professional
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-muted-foreground">
|
||||||
When enabled, users will be
|
When enabled, users will be
|
||||||
|
|
|
@ -43,8 +43,7 @@ export default async function SettingsLayout(props: SettingsLayoutProps) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Organization Policies",
|
title: "Organization Policies",
|
||||||
href: `/admin/idp/${params.idpId}/policies`,
|
href: `/admin/idp/${params.idpId}/policies`
|
||||||
showProfessional: true
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { ColumnDef } from "@tanstack/react-table";
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { ColumnDef } from "@tanstack/react-table";
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
@ -377,9 +372,7 @@ export default function PoliciesPage() {
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
JMESPath to extract role
|
The result of this
|
||||||
information from the ID
|
|
||||||
token. The result of this
|
|
||||||
expression must return the
|
expression must return the
|
||||||
role name as defined in the
|
role name as defined in the
|
||||||
organization as a string.
|
organization as a string.
|
||||||
|
@ -401,13 +394,10 @@ export default function PoliciesPage() {
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
JMESPath to extract
|
This expression must return
|
||||||
organization information
|
thr org ID or true for the
|
||||||
from the ID token. This
|
user to be allowed to access
|
||||||
expression must return thr
|
the organization.
|
||||||
org ID or true for the user
|
|
||||||
to be allowed to access the
|
|
||||||
organization.
|
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
@ -578,8 +568,6 @@ export default function PoliciesPage() {
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
JMESPath to extract role
|
|
||||||
information from the ID token.
|
|
||||||
The result of this expression
|
The result of this expression
|
||||||
must return the role name as
|
must return the role name as
|
||||||
defined in the organization as a
|
defined in the organization as a
|
||||||
|
@ -603,8 +591,6 @@ export default function PoliciesPage() {
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
JMESPath to extract organization
|
|
||||||
information from the ID token.
|
|
||||||
This expression must return the
|
This expression must return the
|
||||||
org ID or true for the user to
|
org ID or true for the user to
|
||||||
be allowed to access the
|
be allowed to access the
|
||||||
|
|
|
@ -192,7 +192,6 @@ export default function Page() {
|
||||||
defaultChecked={form.getValues(
|
defaultChecked={form.getValues(
|
||||||
"autoProvision"
|
"autoProvision"
|
||||||
)}
|
)}
|
||||||
disabled={!isUnlocked()}
|
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => {
|
||||||
form.setValue(
|
form.setValue(
|
||||||
"autoProvision",
|
"autoProvision",
|
||||||
|
@ -200,14 +199,6 @@ export default function Page() {
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{!isUnlocked() && (
|
|
||||||
<Badge
|
|
||||||
variant="outlinePrimary"
|
|
||||||
className="ml-2"
|
|
||||||
>
|
|
||||||
Professional
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-muted-foreground">
|
||||||
When enabled, users will be
|
When enabled, users will be
|
||||||
|
@ -421,7 +412,7 @@ export default function Page() {
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
The JMESPath to the user
|
The path to the user
|
||||||
identifier in the ID
|
identifier in the ID
|
||||||
token
|
token
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
|
@ -442,7 +433,7 @@ export default function Page() {
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
The JMESPath to the
|
The path to the
|
||||||
user's email in the ID
|
user's email in the ID
|
||||||
token
|
token
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
|
@ -463,7 +454,7 @@ export default function Page() {
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
The JMESPath to the
|
The path to the
|
||||||
user's name in the ID
|
user's name in the ID
|
||||||
token
|
token
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { ColumnDef } from "@tanstack/react-table";
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Button } from "@app/components/ui/button";
|
import { Button } from "@app/components/ui/button";
|
||||||
import { MinusCircle, PlusCircle } from "lucide-react";
|
import { MinusCircle, PlusCircle } from "lucide-react";
|
||||||
|
@ -107,35 +102,6 @@ export function SitePriceCalculator({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t pt-4">
|
<div className="border-t pt-4">
|
||||||
{mode === "license" && (
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<span className="text-sm font-medium">
|
|
||||||
License fee:
|
|
||||||
</span>
|
|
||||||
<span className="font-medium">
|
|
||||||
${licenseFlatRate.toFixed(2)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="flex justify-between items-center mt-2">
|
|
||||||
<span className="text-sm font-medium">
|
|
||||||
Price per site:
|
|
||||||
</span>
|
|
||||||
<span className="font-medium">
|
|
||||||
${pricePerSite.toFixed(2)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between items-center mt-2">
|
|
||||||
<span className="text-sm font-medium">
|
|
||||||
Number of sites:
|
|
||||||
</span>
|
|
||||||
<span className="font-medium">{siteCount}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between items-center mt-4 text-lg font-bold">
|
|
||||||
<span>Total:</span>
|
|
||||||
<span>${totalCost.toFixed(2)} / mo</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p className="text-muted-foreground text-sm mt-2 text-center">
|
<p className="text-muted-foreground text-sm mt-2 text-center">
|
||||||
For the most up-to-date pricing and discounts,
|
For the most up-to-date pricing and discounts,
|
||||||
please visit the{" "}
|
please visit the{" "}
|
||||||
|
@ -157,7 +123,7 @@ export function SitePriceCalculator({
|
||||||
<Button variant="outline">Cancel</Button>
|
<Button variant="outline">Cancel</Button>
|
||||||
</CredenzaClose>
|
</CredenzaClose>
|
||||||
<Button onClick={continueToPayment}>
|
<Button onClick={continueToPayment}>
|
||||||
Continue to Payment
|
See Purchase Portal
|
||||||
</Button>
|
</Button>
|
||||||
</CredenzaFooter>
|
</CredenzaFooter>
|
||||||
</CredenzaContent>
|
</CredenzaContent>
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
|
@ -49,7 +44,7 @@ import {
|
||||||
} from "@app/components/Settings";
|
} from "@app/components/Settings";
|
||||||
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
||||||
import { Badge } from "@app/components/ui/badge";
|
import { Badge } from "@app/components/ui/badge";
|
||||||
import { Check, ShieldCheck, ShieldOff } from "lucide-react";
|
import { Check, Heart, InfoIcon, ShieldCheck, ShieldOff } from "lucide-react";
|
||||||
import CopyTextBox from "@app/components/CopyTextBox";
|
import CopyTextBox from "@app/components/CopyTextBox";
|
||||||
import { Progress } from "@app/components/ui/progress";
|
import { Progress } from "@app/components/ui/progress";
|
||||||
import { MinusCircle, PlusCircle } from "lucide-react";
|
import { MinusCircle, PlusCircle } from "lucide-react";
|
||||||
|
@ -57,6 +52,8 @@ import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
|
||||||
import { SitePriceCalculator } from "./components/SitePriceCalculator";
|
import { SitePriceCalculator } from "./components/SitePriceCalculator";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Checkbox } from "@app/components/ui/checkbox";
|
import { Checkbox } from "@app/components/ui/checkbox";
|
||||||
|
import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert";
|
||||||
|
import { useSupporterStatusContext } from "@app/hooks/useSupporterStatusContext";
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
licenseKey: z
|
licenseKey: z
|
||||||
|
@ -95,6 +92,7 @@ export default function LicensePage() {
|
||||||
const [isActivatingLicense, setIsActivatingLicense] = useState(false);
|
const [isActivatingLicense, setIsActivatingLicense] = useState(false);
|
||||||
const [isDeletingLicense, setIsDeletingLicense] = useState(false);
|
const [isDeletingLicense, setIsDeletingLicense] = useState(false);
|
||||||
const [isRecheckingLicense, setIsRecheckingLicense] = useState(false);
|
const [isRecheckingLicense, setIsRecheckingLicense] = useState(false);
|
||||||
|
const { supporterStatus } = useSupporterStatusContext();
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
|
@ -370,6 +368,18 @@ export default function LicensePage() {
|
||||||
description="View and manage license keys in the system"
|
description="View and manage license keys in the system"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Alert variant="neutral" className="mb-6">
|
||||||
|
<InfoIcon className="h-4 w-4" />
|
||||||
|
<AlertTitle className="font-semibold">
|
||||||
|
About Licensing
|
||||||
|
</AlertTitle>
|
||||||
|
<AlertDescription>
|
||||||
|
This is for business and enterprise users who are using
|
||||||
|
Pangolin in a commercial environment. If you are using
|
||||||
|
Pangolin for personal use, you can ignore this section.
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
|
||||||
<SettingsContainer>
|
<SettingsContainer>
|
||||||
<SettingsSectionGrid cols={2}>
|
<SettingsSectionGrid cols={2}>
|
||||||
<SettingsSection>
|
<SettingsSection>
|
||||||
|
@ -387,18 +397,25 @@ export default function LicensePage() {
|
||||||
<Check />
|
<Check />
|
||||||
{licenseStatus?.tier ===
|
{licenseStatus?.tier ===
|
||||||
"PROFESSIONAL"
|
"PROFESSIONAL"
|
||||||
? "Professional License"
|
? "Commercial License"
|
||||||
: licenseStatus?.tier ===
|
: licenseStatus?.tier ===
|
||||||
"ENTERPRISE"
|
"ENTERPRISE"
|
||||||
? "Enterprise License"
|
? "Commercial License"
|
||||||
: "Licensed"}
|
: "Licensed"}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
|
{supporterStatus?.visible ? (
|
||||||
<div className="text-2xl">
|
<div className="text-2xl">
|
||||||
Not Licensed
|
Community Edition
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="text-2xl flex items-center gap-2 text-pink-500">
|
||||||
|
<Heart />
|
||||||
|
Community Edition
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
import ProfileIcon from "@app/components/ProfileIcon";
|
import ProfileIcon from "@app/components/ProfileIcon";
|
||||||
|
import { Separator } from "@app/components/ui/separator";
|
||||||
|
import { priv } from "@app/lib/api";
|
||||||
import { verifySession } from "@app/lib/auth/verifySession";
|
import { verifySession } from "@app/lib/auth/verifySession";
|
||||||
import UserProvider from "@app/providers/UserProvider";
|
import UserProvider from "@app/providers/UserProvider";
|
||||||
|
import { GetLicenseStatusResponse } from "@server/routers/license";
|
||||||
|
import { AxiosResponse } from "axios";
|
||||||
|
import { ExternalLink } from "lucide-react";
|
||||||
import { Metadata } from "next";
|
import { Metadata } from "next";
|
||||||
import { cache } from "react";
|
import { cache } from "react";
|
||||||
|
|
||||||
|
@ -17,21 +22,68 @@ export default async function AuthLayout({ children }: AuthLayoutProps) {
|
||||||
const getUser = cache(verifySession);
|
const getUser = cache(verifySession);
|
||||||
const user = await getUser();
|
const user = await getUser();
|
||||||
|
|
||||||
|
const licenseStatusRes = await cache(
|
||||||
|
async () =>
|
||||||
|
await priv.get<AxiosResponse<GetLicenseStatusResponse>>(
|
||||||
|
"/license/status"
|
||||||
|
)
|
||||||
|
)();
|
||||||
|
const licenseStatus = licenseStatusRes.data.data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col">
|
<div className="h-full flex flex-col">
|
||||||
{user && (
|
{user && (
|
||||||
<UserProvider user={user}>
|
<UserProvider user={user}>
|
||||||
<div className="p-3">
|
<div className="p-3 ml-auto">
|
||||||
<ProfileIcon />
|
<ProfileIcon />
|
||||||
</div>
|
</div>
|
||||||
</UserProvider>
|
</UserProvider>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex-1 flex items-center justify-center">
|
<div className="flex-1 flex items-center justify-center">
|
||||||
<div className="w-full max-w-md p-3">
|
<div className="w-full max-w-md p-3">{children}</div>
|
||||||
{children}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{!(
|
||||||
|
licenseStatus.isHostLicensed && licenseStatus.isLicenseValid
|
||||||
|
) && (
|
||||||
|
<footer className="hidden md:block w-full mt-12 py-3 mb-6 px-4">
|
||||||
|
<div className="container mx-auto flex flex-wrap justify-center items-center h-3 space-x-4 text-sm text-neutral-400 dark:text-neutral-600">
|
||||||
|
<div className="flex items-center space-x-2 whitespace-nowrap">
|
||||||
|
<span>Pangolin</span>
|
||||||
</div>
|
</div>
|
||||||
|
<Separator orientation="vertical" />
|
||||||
|
<a
|
||||||
|
href="https://fossorial.io/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
aria-label="Built by Fossorial"
|
||||||
|
className="flex items-center space-x-2 whitespace-nowrap"
|
||||||
|
>
|
||||||
|
<span>Fossorial</span>
|
||||||
|
<ExternalLink className="w-3 h-3" />
|
||||||
|
</a>
|
||||||
|
<Separator orientation="vertical" />
|
||||||
|
<a
|
||||||
|
href="https://github.com/fosrl/pangolin"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
aria-label="GitHub"
|
||||||
|
className="flex items-center space-x-2 whitespace-nowrap"
|
||||||
|
>
|
||||||
|
<span>Community Edition</span>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="currentColor"
|
||||||
|
className="w-3 h-3"
|
||||||
|
>
|
||||||
|
<path d="M12 0C5.37 0 0 5.373 0 12c0 5.303 3.438 9.8 8.207 11.385.6.11.82-.26.82-.577v-2.17c-3.338.726-4.042-1.61-4.042-1.61-.546-1.385-1.333-1.755-1.333-1.755-1.09-.744.082-.73.082-.73 1.205.085 1.84 1.24 1.84 1.24 1.07 1.835 2.807 1.305 3.492.997.107-.775.42-1.305.763-1.605-2.665-.305-5.467-1.335-5.467-5.93 0-1.31.468-2.382 1.236-3.22-.123-.303-.535-1.523.117-3.176 0 0 1.008-.322 3.3 1.23a11.52 11.52 0 013.006-.403c1.02.005 2.045.137 3.006.403 2.29-1.552 3.295-1.23 3.295-1.23.654 1.653.242 2.873.12 3.176.77.838 1.235 1.91 1.235 3.22 0 4.605-2.805 5.623-5.475 5.92.43.37.814 1.1.814 2.22v3.293c0 .32.217.693.825.576C20.565 21.795 24 17.298 24 12 24 5.373 18.627 0 12 0z" />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@app/components/ui/button";
|
import { Button } from "@app/components/ui/button";
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { IsSupporterKeyVisibleResponse } from "@server/routers/supporterKey";
|
||||||
import LicenseStatusProvider from "@app/providers/LicenseStatusProvider";
|
import LicenseStatusProvider from "@app/providers/LicenseStatusProvider";
|
||||||
import { GetLicenseStatusResponse } from "@server/routers/license";
|
import { GetLicenseStatusResponse } from "@server/routers/license";
|
||||||
import LicenseViolation from "./components/LicenseViolation";
|
import LicenseViolation from "./components/LicenseViolation";
|
||||||
|
import { cache } from "react";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: `Dashboard - Pangolin`,
|
title: `Dashboard - Pangolin`,
|
||||||
|
@ -40,10 +41,12 @@ export default async function RootLayout({
|
||||||
supporterData.visible = res.data.data.visible;
|
supporterData.visible = res.data.data.visible;
|
||||||
supporterData.tier = res.data.data.tier;
|
supporterData.tier = res.data.data.tier;
|
||||||
|
|
||||||
const licenseStatusRes =
|
const licenseStatusRes = await cache(
|
||||||
|
async () =>
|
||||||
await priv.get<AxiosResponse<GetLicenseStatusResponse>>(
|
await priv.get<AxiosResponse<GetLicenseStatusResponse>>(
|
||||||
"/license/status"
|
"/license/status"
|
||||||
);
|
)
|
||||||
|
)();
|
||||||
const licenseStatus = licenseStatusRes.data.data;
|
const licenseStatus = licenseStatusRes.data.data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -68,8 +68,7 @@ export const orgNavItems: SidebarNavItem[] = [
|
||||||
{
|
{
|
||||||
title: "API Keys",
|
title: "API Keys",
|
||||||
href: "/{orgId}/settings/api-keys",
|
href: "/{orgId}/settings/api-keys",
|
||||||
icon: <KeyRound className="h-4 w-4" />,
|
icon: <KeyRound className="h-4 w-4" />
|
||||||
showProfessional: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Settings",
|
title: "Settings",
|
||||||
|
@ -87,8 +86,7 @@ export const adminNavItems: SidebarNavItem[] = [
|
||||||
{
|
{
|
||||||
title: "API Keys",
|
title: "API Keys",
|
||||||
href: "/admin/api-keys",
|
href: "/admin/api-keys",
|
||||||
icon: <KeyRound className="h-4 w-4" />,
|
icon: <KeyRound className="h-4 w-4" />
|
||||||
showProfessional: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Identity Providers",
|
title: "Identity Providers",
|
||||||
|
|
3
src/components/AuthFooter.tsx
Normal file
3
src/components/AuthFooter.tsx
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
export function AuthFooter() {}
|
|
@ -22,6 +22,7 @@ import { Breadcrumbs } from "@app/components/Breadcrumbs";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import { useUserContext } from "@app/hooks/useUserContext";
|
import { useUserContext } from "@app/hooks/useUserContext";
|
||||||
|
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
|
||||||
|
|
||||||
interface LayoutProps {
|
interface LayoutProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
@ -58,6 +59,7 @@ export function Layout({
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const isAdminPage = pathname?.startsWith("/admin");
|
const isAdminPage = pathname?.startsWith("/admin");
|
||||||
const { user } = useUserContext();
|
const { user } = useUserContext();
|
||||||
|
const { isUnlocked } = useLicenseStatusContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-screen overflow-hidden">
|
<div className="flex flex-col h-screen overflow-hidden">
|
||||||
|
@ -207,7 +209,9 @@ export function Layout({
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="flex items-center justify-center gap-1"
|
className="flex items-center justify-center gap-1"
|
||||||
>
|
>
|
||||||
Open Source
|
{!isUnlocked()
|
||||||
|
? "Community Edition"
|
||||||
|
: "Commercial Edition"}
|
||||||
<ExternalLink size={12} />
|
<ExternalLink size={12} />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { CheckboxWithLabel } from "@app/components/ui/checkbox";
|
import { CheckboxWithLabel } from "@app/components/ui/checkbox";
|
||||||
|
@ -26,7 +21,6 @@ function getActionsCategories(root: boolean) {
|
||||||
"Update Organization": "updateOrg",
|
"Update Organization": "updateOrg",
|
||||||
"Get Organization User": "getOrgUser",
|
"Get Organization User": "getOrgUser",
|
||||||
"List Organization Domains": "listOrgDomains",
|
"List Organization Domains": "listOrgDomains",
|
||||||
"Check Org ID": "checkOrgId",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
Site: {
|
Site: {
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { cn } from "@app/lib/cn";
|
import { cn } from "@app/lib/cn";
|
||||||
|
|
|
@ -55,7 +55,7 @@ export function SettingsSectionFooter({
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
return <div className="flex justify-end space-x-2 mt-auto pt-8">{children}</div>;
|
return <div className="flex justify-end space-x-2 mt-auto pt-6">{children}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SettingsSectionGrid({
|
export function SettingsSectionGrid({
|
||||||
|
|
|
@ -193,7 +193,7 @@ export default function SupporterStatus() {
|
||||||
contribution allows us to commit more time to
|
contribution allows us to commit more time to
|
||||||
maintain and add new features to the application for
|
maintain and add new features to the application for
|
||||||
everyone. We will never use this to paywall
|
everyone. We will never use this to paywall
|
||||||
features. This is separate from the Professional
|
features. This is separate from any Commercial
|
||||||
Edition.
|
Edition.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ import {
|
||||||
CardTitle
|
CardTitle
|
||||||
} from "@app/components/ui/card";
|
} from "@app/components/ui/card";
|
||||||
|
|
||||||
interface DataTableProps<TData, TValue> {
|
type DataTableProps<TData, TValue> = {
|
||||||
columns: ColumnDef<TData, TValue>[];
|
columns: ColumnDef<TData, TValue>[];
|
||||||
data: TData[];
|
data: TData[];
|
||||||
title?: string;
|
title?: string;
|
||||||
|
@ -39,7 +39,11 @@ interface DataTableProps<TData, TValue> {
|
||||||
onAdd?: () => void;
|
onAdd?: () => void;
|
||||||
searchPlaceholder?: string;
|
searchPlaceholder?: string;
|
||||||
searchColumn?: string;
|
searchColumn?: string;
|
||||||
}
|
defaultSort?: {
|
||||||
|
id: string;
|
||||||
|
desc: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export function DataTable<TData, TValue>({
|
export function DataTable<TData, TValue>({
|
||||||
columns,
|
columns,
|
||||||
|
@ -48,9 +52,12 @@ export function DataTable<TData, TValue>({
|
||||||
addButtonText,
|
addButtonText,
|
||||||
onAdd,
|
onAdd,
|
||||||
searchPlaceholder = "Search...",
|
searchPlaceholder = "Search...",
|
||||||
searchColumn = "name"
|
searchColumn = "name",
|
||||||
|
defaultSort
|
||||||
}: DataTableProps<TData, TValue>) {
|
}: DataTableProps<TData, TValue>) {
|
||||||
const [sorting, setSorting] = useState<SortingState>([]);
|
const [sorting, setSorting] = useState<SortingState>(
|
||||||
|
defaultSort ? [defaultSort] : []
|
||||||
|
);
|
||||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
|
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
|
||||||
const [globalFilter, setGlobalFilter] = useState<any>([]);
|
const [globalFilter, setGlobalFilter] = useState<any>([]);
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { GetApiKeyResponse } from "@server/routers/apiKeys";
|
import { GetApiKeyResponse } from "@server/routers/apiKeys";
|
||||||
import { createContext } from "react";
|
import { createContext } from "react";
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import { LicenseStatus } from "@server/license/license";
|
import { LicenseStatus } from "@server/license/license";
|
||||||
import { createContext } from "react";
|
import { createContext } from "react";
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import ApiKeyContext from "@app/contexts/apiKeyContext";
|
import ApiKeyContext from "@app/contexts/apiKeyContext";
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
import LicenseStatusContext from "@app/contexts/licenseStatusContext";
|
import LicenseStatusContext from "@app/contexts/licenseStatusContext";
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import ApiKeyContext from "@app/contexts/apiKeyContext";
|
import ApiKeyContext from "@app/contexts/apiKeyContext";
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
// This file is licensed under the Fossorial Commercial License.
|
|
||||||
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Fossorial LLC. All rights reserved.
|
|
||||||
|
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import LicenseStatusContext from "@app/contexts/licenseStatusContext";
|
import LicenseStatusContext from "@app/contexts/licenseStatusContext";
|
||||||
|
|
Loading…
Add table
Reference in a new issue