mirror of
https://github.com/fosrl/pangolin.git
synced 2025-07-28 06:34:50 +02:00
Merge branch 'dev' into clients-pops
This commit is contained in:
commit
acf25e8ad7
20 changed files with 14219 additions and 14178 deletions
28179
package-lock.json
generated
28179
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -20,8 +20,9 @@ const externalPort = config.getRawConfig().server.external_port;
|
|||
export function createApiServer() {
|
||||
const apiServer = express();
|
||||
|
||||
if (config.getRawConfig().server.trust_proxy) {
|
||||
apiServer.set("trust proxy", 1);
|
||||
const trustProxy = config.getRawConfig().server.trust_proxy;
|
||||
if (trustProxy) {
|
||||
apiServer.set("trust proxy", trustProxy);
|
||||
}
|
||||
|
||||
const corsConfig = config.getRawConfig().server.cors;
|
||||
|
|
|
@ -29,14 +29,15 @@ class RedisManager {
|
|||
|
||||
private getRedisConfig(): RedisOptions {
|
||||
const redisConfig = config.getRawConfig().redis!;
|
||||
const opts: RedisOptions = {
|
||||
const opts: RedisOptions = {
|
||||
host: redisConfig.host!,
|
||||
port: redisConfig.port!,
|
||||
password: redisConfig.password,
|
||||
db: redisConfig.db,
|
||||
tls: {
|
||||
rejectUnauthorized: false
|
||||
},
|
||||
rejectUnauthorized:
|
||||
redisConfig.tls?.reject_unauthorized || false
|
||||
}
|
||||
};
|
||||
return opts;
|
||||
}
|
||||
|
|
|
@ -106,7 +106,7 @@ export const configSchema = z
|
|||
credentials: z.boolean().optional()
|
||||
})
|
||||
.optional(),
|
||||
trust_proxy: z.boolean().optional().default(true),
|
||||
trust_proxy: z.number().int().gte(0).optional().default(1),
|
||||
secret: z
|
||||
.string()
|
||||
.optional()
|
||||
|
@ -133,7 +133,7 @@ export const configSchema = z
|
|||
db: z.number().int().nonnegative().optional().default(0),
|
||||
tls: z
|
||||
.object({
|
||||
rejectUnauthorized: z.boolean().optional().default(true)
|
||||
reject_unauthorized: z.boolean().optional().default(true)
|
||||
})
|
||||
.optional()
|
||||
})
|
||||
|
|
|
@ -23,8 +23,8 @@ export const loginBodySchema = z
|
|||
.object({
|
||||
email: z
|
||||
.string()
|
||||
.email()
|
||||
.transform((v) => v.toLowerCase()),
|
||||
.toLowerCase()
|
||||
.email(),
|
||||
password: z.string(),
|
||||
code: z.string().optional()
|
||||
})
|
||||
|
|
|
@ -20,8 +20,8 @@ export const requestPasswordResetBody = z
|
|||
.object({
|
||||
email: z
|
||||
.string()
|
||||
.email()
|
||||
.transform((v) => v.toLowerCase())
|
||||
.toLowerCase()
|
||||
.email(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
|
|
|
@ -21,8 +21,8 @@ export const resetPasswordBody = z
|
|||
.object({
|
||||
email: z
|
||||
.string()
|
||||
.email()
|
||||
.transform((v) => v.toLowerCase()),
|
||||
.toLowerCase()
|
||||
.email(),
|
||||
token: z.string(), // reset secret code
|
||||
newPassword: passwordSchema,
|
||||
code: z.string().optional() // 2fa code
|
||||
|
|
|
@ -26,8 +26,8 @@ import { UserType } from "@server/types/UserTypes";
|
|||
export const signupBodySchema = z.object({
|
||||
email: z
|
||||
.string()
|
||||
.email()
|
||||
.transform((v) => v.toLowerCase()),
|
||||
.toLowerCase()
|
||||
.email(),
|
||||
password: passwordSchema,
|
||||
inviteToken: z.string().optional(),
|
||||
inviteId: z.string().optional()
|
||||
|
|
|
@ -172,10 +172,10 @@ export async function validateOidcCallback(
|
|||
const claims = arctic.decodeIdToken(idToken);
|
||||
logger.debug("ID token claims", { claims });
|
||||
|
||||
const userIdentifier = jmespath.search(
|
||||
let userIdentifier = jmespath.search(
|
||||
claims,
|
||||
existingIdp.idpOidcConfig.identifierPath
|
||||
);
|
||||
) as string | null;
|
||||
|
||||
if (!userIdentifier) {
|
||||
return next(
|
||||
|
@ -186,6 +186,8 @@ export async function validateOidcCallback(
|
|||
);
|
||||
}
|
||||
|
||||
userIdentifier = userIdentifier.toLowerCase();
|
||||
|
||||
logger.debug("User identifier", { userIdentifier });
|
||||
|
||||
let email = null;
|
||||
|
@ -209,6 +211,10 @@ export async function validateOidcCallback(
|
|||
logger.debug("User email", { email });
|
||||
logger.debug("User name", { name });
|
||||
|
||||
if (email) {
|
||||
email = email.toLowerCase();
|
||||
}
|
||||
|
||||
const [existingUser] = await db
|
||||
.select()
|
||||
.from(users)
|
||||
|
|
|
@ -22,8 +22,8 @@ const authWithWhitelistBodySchema = z
|
|||
.object({
|
||||
email: z
|
||||
.string()
|
||||
.email()
|
||||
.transform((v) => v.toLowerCase()),
|
||||
.toLowerCase()
|
||||
.email(),
|
||||
otp: z.string().optional()
|
||||
})
|
||||
.strict();
|
||||
|
|
|
@ -39,7 +39,7 @@ const createHttpResourceSchema = z
|
|||
isBaseDomain: z.boolean().optional(),
|
||||
siteId: z.number(),
|
||||
http: z.boolean(),
|
||||
protocol: z.string(),
|
||||
protocol: z.enum(["tcp", "udp"]),
|
||||
domainId: z.string()
|
||||
})
|
||||
.strict()
|
||||
|
@ -71,7 +71,7 @@ const createRawResourceSchema = z
|
|||
name: z.string().min(1).max(255),
|
||||
siteId: z.number(),
|
||||
http: z.boolean(),
|
||||
protocol: z.string(),
|
||||
protocol: z.enum(["tcp", "udp"]),
|
||||
proxyPort: z.number().int().min(1).max(65535)
|
||||
})
|
||||
.strict()
|
||||
|
@ -85,7 +85,7 @@ const createRawResourceSchema = z
|
|||
return true;
|
||||
},
|
||||
{
|
||||
message: "Proxy port cannot be set"
|
||||
message: "Raw resources are not allowed"
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -400,7 +400,7 @@ async function createRawResource(
|
|||
resourceId: newResource[0].resourceId
|
||||
});
|
||||
|
||||
if (req.userOrgRoleId != adminRole[0].roleId) {
|
||||
if (req.user && req.userOrgRoleId != adminRole[0].roleId) {
|
||||
// make sure the user can access the resource
|
||||
await trx.insert(userResources).values({
|
||||
userId: req.user?.userId!,
|
||||
|
|
|
@ -21,6 +21,7 @@ const bodySchema = z
|
|||
.object({
|
||||
email: z
|
||||
.string()
|
||||
.toLowerCase()
|
||||
.optional()
|
||||
.refine((data) => {
|
||||
if (data) {
|
||||
|
@ -28,7 +29,7 @@ const bodySchema = z
|
|||
}
|
||||
return true;
|
||||
}),
|
||||
username: z.string().nonempty(),
|
||||
username: z.string().nonempty().toLowerCase(),
|
||||
name: z.string().optional(),
|
||||
type: z.enum(["internal", "oidc"]).optional(),
|
||||
idpId: z.number().optional(),
|
||||
|
|
|
@ -30,8 +30,8 @@ const inviteUserBodySchema = z
|
|||
.object({
|
||||
email: z
|
||||
.string()
|
||||
.email()
|
||||
.transform((v) => v.toLowerCase()),
|
||||
.toLowerCase()
|
||||
.email(),
|
||||
roleId: z.number(),
|
||||
validHours: z.number().gt(0).lte(168),
|
||||
sendEmail: z.boolean().optional(),
|
||||
|
|
|
@ -2,14 +2,16 @@ import { migrate } from "drizzle-orm/node-postgres/migrator";
|
|||
import { db } from "../db/pg";
|
||||
import semver from "semver";
|
||||
import { versionMigrations } from "../db/pg";
|
||||
import { __DIRNAME, APP_PATH, APP_VERSION } from "@server/lib/consts";
|
||||
import { __DIRNAME, APP_VERSION } from "@server/lib/consts";
|
||||
import path from "path";
|
||||
import m1 from "./scriptsSqlite/1.6.0";
|
||||
|
||||
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
|
||||
// EXCEPT FOR THE DATABASE AND THE SCHEMA
|
||||
|
||||
// Define the migration list with versions and their corresponding functions
|
||||
const migrations = [
|
||||
{ version: "1.6.0", run: m1 }
|
||||
// Add new migrations here as they are created
|
||||
] as {
|
||||
version: string;
|
||||
|
|
|
@ -21,6 +21,7 @@ import m17 from "./scriptsSqlite/1.1.0";
|
|||
import m18 from "./scriptsSqlite/1.2.0";
|
||||
import m19 from "./scriptsSqlite/1.3.0";
|
||||
import m20 from "./scriptsSqlite/1.5.0";
|
||||
import m21 from "./scriptsSqlite/1.6.0";
|
||||
|
||||
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
|
||||
// EXCEPT FOR THE DATABASE AND THE SCHEMA
|
||||
|
@ -42,6 +43,7 @@ const migrations = [
|
|||
{ version: "1.2.0", run: m18 },
|
||||
{ version: "1.3.0", run: m19 },
|
||||
{ version: "1.5.0", run: m20 },
|
||||
{ version: "1.6.0", run: m21 }
|
||||
// Add new migrations here as they are created
|
||||
] as const;
|
||||
|
||||
|
|
57
server/setup/scriptsPg/1.6.0.ts
Normal file
57
server/setup/scriptsPg/1.6.0.ts
Normal file
|
@ -0,0 +1,57 @@
|
|||
import { db } from "@server/db/pg/driver";
|
||||
import { configFilePath1, configFilePath2 } from "@server/lib/consts";
|
||||
import { sql } from "drizzle-orm";
|
||||
import fs from "fs";
|
||||
import yaml from "js-yaml";
|
||||
|
||||
const version = "1.6.0";
|
||||
|
||||
export default async function migration() {
|
||||
console.log(`Running setup script ${version}...`);
|
||||
|
||||
try {
|
||||
db.execute(sql`UPDATE 'user' SET email = LOWER(email);`);
|
||||
db.execute(sql`UPDATE 'user' SET username = LOWER(username);`);
|
||||
console.log(`Migrated database schema`);
|
||||
} catch (e) {
|
||||
console.log("Unable to make all usernames and emails lowercase");
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
try {
|
||||
// Determine which config file exists
|
||||
const filePaths = [configFilePath1, configFilePath2];
|
||||
let filePath = "";
|
||||
for (const path of filePaths) {
|
||||
if (fs.existsSync(path)) {
|
||||
filePath = path;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!filePath) {
|
||||
throw new Error(
|
||||
`No config file found (expected config.yml or config.yaml).`
|
||||
);
|
||||
}
|
||||
|
||||
// Read and parse the YAML file
|
||||
let rawConfig: any;
|
||||
const fileContents = fs.readFileSync(filePath, "utf8");
|
||||
rawConfig = yaml.load(fileContents);
|
||||
|
||||
if (rawConfig.server?.trust_proxy) {
|
||||
rawConfig.server.trust_proxy = 1;
|
||||
}
|
||||
|
||||
// Write the updated YAML back to the file
|
||||
const updatedYaml = yaml.dump(rawConfig);
|
||||
fs.writeFileSync(filePath, updatedYaml, "utf8");
|
||||
|
||||
console.log(`Set trust_proxy to 1 in config file`);
|
||||
} catch (e) {
|
||||
console.log(`Unable to migrate config file. Error: ${e}`);
|
||||
}
|
||||
|
||||
console.log(`${version} migration complete`);
|
||||
}
|
66
server/setup/scriptsSqlite/1.6.0.ts
Normal file
66
server/setup/scriptsSqlite/1.6.0.ts
Normal file
|
@ -0,0 +1,66 @@
|
|||
import { APP_PATH, configFilePath1, configFilePath2 } from "@server/lib/consts";
|
||||
import Database from "better-sqlite3";
|
||||
import fs from "fs";
|
||||
import yaml from "js-yaml";
|
||||
import path from "path";
|
||||
|
||||
const version = "1.6.0";
|
||||
|
||||
export default async function migration() {
|
||||
console.log(`Running setup script ${version}...`);
|
||||
|
||||
const location = path.join(APP_PATH, "db", "db.sqlite");
|
||||
const db = new Database(location);
|
||||
|
||||
try {
|
||||
db.pragma("foreign_keys = OFF");
|
||||
db.transaction(() => {
|
||||
db.exec(`
|
||||
UPDATE 'user' SET email = LOWER(email);
|
||||
UPDATE 'user' SET username = LOWER(username);
|
||||
`);
|
||||
})(); // <-- executes the transaction immediately
|
||||
db.pragma("foreign_keys = ON");
|
||||
console.log(`Migrated database schema`);
|
||||
} catch (e) {
|
||||
console.log("Unable to make all usernames and emails lowercase");
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
try {
|
||||
// Determine which config file exists
|
||||
const filePaths = [configFilePath1, configFilePath2];
|
||||
let filePath = "";
|
||||
for (const path of filePaths) {
|
||||
if (fs.existsSync(path)) {
|
||||
filePath = path;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!filePath) {
|
||||
throw new Error(
|
||||
`No config file found (expected config.yml or config.yaml).`
|
||||
);
|
||||
}
|
||||
|
||||
// Read and parse the YAML file
|
||||
let rawConfig: any;
|
||||
const fileContents = fs.readFileSync(filePath, "utf8");
|
||||
rawConfig = yaml.load(fileContents);
|
||||
|
||||
if (rawConfig.server?.trust_proxy) {
|
||||
rawConfig.server.trust_proxy = 1;
|
||||
}
|
||||
|
||||
// Write the updated YAML back to the file
|
||||
const updatedYaml = yaml.dump(rawConfig);
|
||||
fs.writeFileSync(filePath, updatedYaml, "utf8");
|
||||
|
||||
console.log(`Set trust_proxy to 1 in config file`);
|
||||
} catch (e) {
|
||||
console.log(`Unable to migrate config file. Please do it manually. Error: ${e}`);
|
||||
}
|
||||
|
||||
console.log(`${version} migration complete`);
|
||||
}
|
|
@ -42,6 +42,7 @@ export default function StepperForm() {
|
|||
const [loading, setLoading] = useState(false);
|
||||
const [isChecked, setIsChecked] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [orgCreated, setOrgCreated] = useState(false);
|
||||
|
||||
const orgSchema = z.object({
|
||||
orgName: z.string().min(1, { message: t('orgNameRequired') }),
|
||||
|
@ -83,7 +84,7 @@ export default function StepperForm() {
|
|||
};
|
||||
|
||||
const checkOrgIdAvailability = useCallback(async (value: string) => {
|
||||
if (loading) {
|
||||
if (loading || orgCreated) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
|
@ -96,7 +97,7 @@ export default function StepperForm() {
|
|||
} catch (error) {
|
||||
setOrgIdTaken(false);
|
||||
}
|
||||
}, []);
|
||||
}, [loading, orgCreated, api]);
|
||||
|
||||
const debouncedCheckOrgIdAvailability = useCallback(
|
||||
debounce(checkOrgIdAvailability, 300),
|
||||
|
@ -129,7 +130,7 @@ export default function StepperForm() {
|
|||
});
|
||||
|
||||
if (res && res.status === 201) {
|
||||
// setCurrentStep("site");
|
||||
setOrgCreated(true);
|
||||
router.push(`/${values.orgId}/settings/sites/create`);
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
|
@ -78,10 +78,12 @@ export function Layout({
|
|||
}
|
||||
|
||||
if (lightOrDark === "light") {
|
||||
return "/logo/word_mark_black.png";
|
||||
// return "/logo/word_mark_black.png";
|
||||
return "/logo/pangolin_orange.svg";
|
||||
}
|
||||
|
||||
return "/logo/word_mark_white.png";
|
||||
// return "/logo/word_mark_white.png";
|
||||
return "/logo/pangolin_orange.svg";
|
||||
}
|
||||
|
||||
setPath(getPath());
|
||||
|
@ -170,8 +172,8 @@ export function Layout({
|
|||
<Image
|
||||
src={path}
|
||||
alt="Pangolin Logo"
|
||||
width={110}
|
||||
height={25}
|
||||
width={35}
|
||||
height={35}
|
||||
priority={true}
|
||||
quality={25}
|
||||
/>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { cookies } from "next/headers";
|
||||
import { cookies, headers } from "next/headers";
|
||||
import { pullEnv } from "../pullEnv";
|
||||
|
||||
export async function authCookieHeader() {
|
||||
|
@ -7,9 +7,16 @@ export async function authCookieHeader() {
|
|||
const allCookies = await cookies();
|
||||
const cookieName = env.server.sessionCookieName;
|
||||
const sessionId = allCookies.get(cookieName)?.value ?? null;
|
||||
|
||||
// all other headers
|
||||
// this is needed to pass through x-forwarded-for, x-forwarded-proto, etc.
|
||||
const otherHeaders = await headers();
|
||||
const otherHeadersObject = Object.fromEntries(otherHeaders.entries());
|
||||
|
||||
return {
|
||||
headers: {
|
||||
Cookie: `${cookieName}=${sessionId}`,
|
||||
...otherHeadersObject
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue