mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-25 22:36:38 +00:00
Merge branch 'dev' into transfer-resource-to-new-site
This commit is contained in:
@@ -1,20 +1,17 @@
|
||||
import { generateSessionToken } from "@server/auth/sessions/app";
|
||||
import db from "@server/db";
|
||||
import { resourceAccessToken, resources } from "@server/db/schema";
|
||||
import { resources } from "@server/db/schema";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import response from "@server/lib/response";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import createHttpError from "http-errors";
|
||||
import { z } from "zod";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import {
|
||||
createResourceSession,
|
||||
serializeResourceSessionCookie
|
||||
} from "@server/auth/sessions/resource";
|
||||
import config from "@server/lib/config";
|
||||
import { createResourceSession } from "@server/auth/sessions/resource";
|
||||
import logger from "@server/logger";
|
||||
import { verifyResourceAccessToken } from "@server/auth/verifyResourceAccessToken";
|
||||
import config from "@server/lib/config";
|
||||
|
||||
const authWithAccessTokenBodySchema = z
|
||||
.object({
|
||||
@@ -86,6 +83,11 @@ export async function authWithAccessToken(
|
||||
});
|
||||
|
||||
if (!valid) {
|
||||
if (config.getRawConfig().app.log_failed_attempts) {
|
||||
logger.info(
|
||||
`Resource access token invalid. Resource ID: ${resource.resourceId}. IP: ${req.ip}.`
|
||||
);
|
||||
}
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.UNAUTHORIZED,
|
||||
@@ -108,13 +110,11 @@ export async function authWithAccessToken(
|
||||
resourceId,
|
||||
token,
|
||||
accessTokenId: tokenItem.accessTokenId,
|
||||
sessionLength: tokenItem.sessionLength,
|
||||
expiresAt: tokenItem.expiresAt,
|
||||
doNotExtend: tokenItem.expiresAt ? true : false
|
||||
isRequestToken: true,
|
||||
expiresAt: Date.now() + 1000 * 30, // 30 seconds
|
||||
sessionLength: 1000 * 30,
|
||||
doNotExtend: true
|
||||
});
|
||||
const cookieName = `${config.getRawConfig().server.resource_session_cookie_name}_${resource.resourceId}`;
|
||||
const cookie = serializeResourceSessionCookie(cookieName, token);
|
||||
res.appendHeader("Set-Cookie", cookie);
|
||||
|
||||
return response<AuthWithAccessTokenResponse>(res, {
|
||||
data: {
|
||||
|
||||
@@ -9,13 +9,10 @@ import { NextFunction, Request, Response } from "express";
|
||||
import createHttpError from "http-errors";
|
||||
import { z } from "zod";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import {
|
||||
createResourceSession,
|
||||
serializeResourceSessionCookie
|
||||
} from "@server/auth/sessions/resource";
|
||||
import config from "@server/lib/config";
|
||||
import { createResourceSession } from "@server/auth/sessions/resource";
|
||||
import logger from "@server/logger";
|
||||
import { verifyPassword } from "@server/auth/password";
|
||||
import config from "@server/lib/config";
|
||||
|
||||
export const authWithPasswordBodySchema = z
|
||||
.object({
|
||||
@@ -84,7 +81,7 @@ export async function authWithPassword(
|
||||
|
||||
if (!org) {
|
||||
return next(
|
||||
createHttpError(HttpCode.BAD_REQUEST, "Resource does not exist")
|
||||
createHttpError(HttpCode.BAD_REQUEST, "Org does not exist")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -111,6 +108,11 @@ export async function authWithPassword(
|
||||
definedPassword.passwordHash
|
||||
);
|
||||
if (!validPassword) {
|
||||
if (config.getRawConfig().app.log_failed_attempts) {
|
||||
logger.info(
|
||||
`Resource password incorrect. Resource ID: ${resource.resourceId}. IP: ${req.ip}.`
|
||||
);
|
||||
}
|
||||
return next(
|
||||
createHttpError(HttpCode.UNAUTHORIZED, "Incorrect password")
|
||||
);
|
||||
@@ -120,11 +122,12 @@ export async function authWithPassword(
|
||||
await createResourceSession({
|
||||
resourceId,
|
||||
token,
|
||||
passwordId: definedPassword.passwordId
|
||||
passwordId: definedPassword.passwordId,
|
||||
isRequestToken: true,
|
||||
expiresAt: Date.now() + 1000 * 30, // 30 seconds
|
||||
sessionLength: 1000 * 30,
|
||||
doNotExtend: true
|
||||
});
|
||||
const cookieName = `${config.getRawConfig().server.resource_session_cookie_name}_${resource.resourceId}`;
|
||||
const cookie = serializeResourceSessionCookie(cookieName, token);
|
||||
res.appendHeader("Set-Cookie", cookie);
|
||||
|
||||
return response<AuthWithPasswordResponse>(res, {
|
||||
data: {
|
||||
|
||||
@@ -1,29 +1,17 @@
|
||||
import { verify } from "@node-rs/argon2";
|
||||
import { generateSessionToken } from "@server/auth/sessions/app";
|
||||
import db from "@server/db";
|
||||
import {
|
||||
orgs,
|
||||
resourceOtp,
|
||||
resourcePincode,
|
||||
resources,
|
||||
resourceWhitelist
|
||||
} from "@server/db/schema";
|
||||
import { orgs, resourcePincode, resources } from "@server/db/schema";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import response from "@server/lib/response";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import createHttpError from "http-errors";
|
||||
import { z } from "zod";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import {
|
||||
createResourceSession,
|
||||
serializeResourceSessionCookie
|
||||
} from "@server/auth/sessions/resource";
|
||||
import { createResourceSession } from "@server/auth/sessions/resource";
|
||||
import logger from "@server/logger";
|
||||
import config from "@server/lib/config";
|
||||
import { AuthWithPasswordResponse } from "./authWithPassword";
|
||||
import { isValidOtp, sendResourceOtpEmail } from "@server/auth/resourceOtp";
|
||||
import { verifyPassword } from "@server/auth/password";
|
||||
import config from "@server/lib/config";
|
||||
|
||||
export const authWithPincodeBodySchema = z
|
||||
.object({
|
||||
@@ -109,19 +97,21 @@ export async function authWithPincode(
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.UNAUTHORIZED,
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
"Resource has no pincode protection"
|
||||
)
|
||||
"Resource has no pincode protection"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const validPincode = verifyPassword(
|
||||
const validPincode = await verifyPassword(
|
||||
pincode,
|
||||
definedPincode.pincodeHash
|
||||
);
|
||||
if (!validPincode) {
|
||||
if (config.getRawConfig().app.log_failed_attempts) {
|
||||
logger.info(
|
||||
`Resource pin code incorrect. Resource ID: ${resource.resourceId}. IP: ${req.ip}.`
|
||||
);
|
||||
}
|
||||
return next(
|
||||
createHttpError(HttpCode.UNAUTHORIZED, "Incorrect PIN")
|
||||
);
|
||||
@@ -131,11 +121,12 @@ export async function authWithPincode(
|
||||
await createResourceSession({
|
||||
resourceId,
|
||||
token,
|
||||
pincodeId: definedPincode.pincodeId
|
||||
pincodeId: definedPincode.pincodeId,
|
||||
isRequestToken: true,
|
||||
expiresAt: Date.now() + 1000 * 30, // 30 seconds
|
||||
sessionLength: 1000 * 30,
|
||||
doNotExtend: true
|
||||
});
|
||||
const cookieName = `${config.getRawConfig().server.resource_session_cookie_name}_${resource.resourceId}`;
|
||||
const cookie = serializeResourceSessionCookie(cookieName, token);
|
||||
res.appendHeader("Set-Cookie", cookie);
|
||||
|
||||
return response<AuthWithPincodeResponse>(res, {
|
||||
data: {
|
||||
|
||||
@@ -3,7 +3,6 @@ import db from "@server/db";
|
||||
import {
|
||||
orgs,
|
||||
resourceOtp,
|
||||
resourcePassword,
|
||||
resources,
|
||||
resourceWhitelist
|
||||
} from "@server/db/schema";
|
||||
@@ -14,17 +13,17 @@ import { NextFunction, Request, Response } from "express";
|
||||
import createHttpError from "http-errors";
|
||||
import { z } from "zod";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import {
|
||||
createResourceSession,
|
||||
serializeResourceSessionCookie
|
||||
} from "@server/auth/sessions/resource";
|
||||
import config from "@server/lib/config";
|
||||
import { createResourceSession } from "@server/auth/sessions/resource";
|
||||
import { isValidOtp, sendResourceOtpEmail } from "@server/auth/resourceOtp";
|
||||
import logger from "@server/logger";
|
||||
import config from "@server/lib/config";
|
||||
|
||||
const authWithWhitelistBodySchema = z
|
||||
.object({
|
||||
email: z.string().email(),
|
||||
email: z
|
||||
.string()
|
||||
.email()
|
||||
.transform((v) => v.toLowerCase()),
|
||||
otp: z.string().optional()
|
||||
})
|
||||
.strict();
|
||||
@@ -90,20 +89,53 @@ export async function authWithWhitelist(
|
||||
.leftJoin(orgs, eq(orgs.orgId, resources.orgId))
|
||||
.limit(1);
|
||||
|
||||
const resource = result?.resources;
|
||||
const org = result?.orgs;
|
||||
const whitelistedEmail = result?.resourceWhitelist;
|
||||
let resource = result?.resources;
|
||||
let org = result?.orgs;
|
||||
let whitelistedEmail = result?.resourceWhitelist;
|
||||
|
||||
if (!whitelistedEmail) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.UNAUTHORIZED,
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
"Email is not whitelisted"
|
||||
// if email is not found, check for wildcard email
|
||||
const wildcard = "*@" + email.split("@")[1];
|
||||
|
||||
logger.debug("Checking for wildcard email: " + wildcard);
|
||||
|
||||
const [result] = await db
|
||||
.select()
|
||||
.from(resourceWhitelist)
|
||||
.where(
|
||||
and(
|
||||
eq(resourceWhitelist.resourceId, resourceId),
|
||||
eq(resourceWhitelist.email, wildcard)
|
||||
)
|
||||
)
|
||||
);
|
||||
.leftJoin(
|
||||
resources,
|
||||
eq(resources.resourceId, resourceWhitelist.resourceId)
|
||||
)
|
||||
.leftJoin(orgs, eq(orgs.orgId, resources.orgId))
|
||||
.limit(1);
|
||||
|
||||
resource = result?.resources;
|
||||
org = result?.orgs;
|
||||
whitelistedEmail = result?.resourceWhitelist;
|
||||
|
||||
// if wildcard is still not found, return unauthorized
|
||||
if (!whitelistedEmail) {
|
||||
if (config.getRawConfig().app.log_failed_attempts) {
|
||||
logger.info(
|
||||
`Email is not whitelisted. Email: ${email}. IP: ${req.ip}.`
|
||||
);
|
||||
}
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.UNAUTHORIZED,
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
"Email is not whitelisted"
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!org) {
|
||||
@@ -125,6 +157,11 @@ export async function authWithWhitelist(
|
||||
otp
|
||||
);
|
||||
if (!isValidCode) {
|
||||
if (config.getRawConfig().app.log_failed_attempts) {
|
||||
logger.info(
|
||||
`Resource email otp incorrect. Resource ID: ${resource.resourceId}. Email: ${email}. IP: ${req.ip}.`
|
||||
);
|
||||
}
|
||||
return next(
|
||||
createHttpError(HttpCode.UNAUTHORIZED, "Incorrect OTP")
|
||||
);
|
||||
@@ -175,11 +212,12 @@ export async function authWithWhitelist(
|
||||
await createResourceSession({
|
||||
resourceId,
|
||||
token,
|
||||
whitelistId: whitelistedEmail.whitelistId
|
||||
whitelistId: whitelistedEmail.whitelistId,
|
||||
isRequestToken: true,
|
||||
expiresAt: Date.now() + 1000 * 30, // 30 seconds
|
||||
sessionLength: 1000 * 30,
|
||||
doNotExtend: true
|
||||
});
|
||||
const cookieName = `${config.getRawConfig().server.resource_session_cookie_name}_${resource.resourceId}`;
|
||||
const cookie = serializeResourceSessionCookie(cookieName, token);
|
||||
res.appendHeader("Set-Cookie", cookie);
|
||||
|
||||
return response<AuthWithWhitelistResponse>(res, {
|
||||
data: {
|
||||
|
||||
@@ -16,8 +16,8 @@ import createHttpError from "http-errors";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import stoi from "@server/lib/stoi";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { subdomainSchema } from "@server/schemas/subdomainSchema";
|
||||
import logger from "@server/logger";
|
||||
import { subdomainSchema } from "@server/schemas/subdomainSchema";
|
||||
|
||||
const createResourceParamsSchema = z
|
||||
.object({
|
||||
@@ -28,10 +28,42 @@ const createResourceParamsSchema = z
|
||||
|
||||
const createResourceSchema = z
|
||||
.object({
|
||||
subdomain: z.string().optional(),
|
||||
name: z.string().min(1).max(255),
|
||||
subdomain: subdomainSchema
|
||||
siteId: z.number(),
|
||||
http: z.boolean(),
|
||||
protocol: z.string(),
|
||||
proxyPort: z.number().optional()
|
||||
})
|
||||
.strict();
|
||||
.refine(
|
||||
(data) => {
|
||||
if (!data.http) {
|
||||
return z
|
||||
.number()
|
||||
.int()
|
||||
.min(1)
|
||||
.max(65535)
|
||||
.safeParse(data.proxyPort).success;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message: "Invalid port number",
|
||||
path: ["proxyPort"]
|
||||
}
|
||||
)
|
||||
.refine(
|
||||
(data) => {
|
||||
if (data.http) {
|
||||
return subdomainSchema.safeParse(data.subdomain).success;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message: "Invalid subdomain",
|
||||
path: ["subdomain"]
|
||||
}
|
||||
);
|
||||
|
||||
export type CreateResourceResponse = Resource;
|
||||
|
||||
@@ -51,7 +83,7 @@ export async function createResource(
|
||||
);
|
||||
}
|
||||
|
||||
let { name, subdomain } = parsedBody.data;
|
||||
let { name, subdomain, protocol, proxyPort, http } = parsedBody.data;
|
||||
|
||||
// Validate request params
|
||||
const parsedParams = createResourceParamsSchema.safeParse(req.params);
|
||||
@@ -89,15 +121,64 @@ export async function createResource(
|
||||
}
|
||||
|
||||
const fullDomain = `${subdomain}.${org[0].domain}`;
|
||||
// if http is false check to see if there is already a resource with the same port and protocol
|
||||
if (!http) {
|
||||
const existingResource = await db
|
||||
.select()
|
||||
.from(resources)
|
||||
.where(
|
||||
and(
|
||||
eq(resources.protocol, protocol),
|
||||
eq(resources.proxyPort, proxyPort!)
|
||||
)
|
||||
);
|
||||
|
||||
if (existingResource.length > 0) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.CONFLICT,
|
||||
"Resource with that protocol and port already exists"
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (proxyPort === 443 || proxyPort === 80) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
"Port 80 and 443 are reserved for https resources"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// make sure the full domain is unique
|
||||
const existingResource = await db
|
||||
.select()
|
||||
.from(resources)
|
||||
.where(eq(resources.fullDomain, fullDomain));
|
||||
|
||||
if (existingResource.length > 0) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.CONFLICT,
|
||||
"Resource with that domain already exists"
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await db.transaction(async (trx) => {
|
||||
const newResource = await trx
|
||||
.insert(resources)
|
||||
.values({
|
||||
siteId,
|
||||
fullDomain,
|
||||
fullDomain: http ? fullDomain : null,
|
||||
orgId,
|
||||
name,
|
||||
subdomain,
|
||||
http,
|
||||
protocol,
|
||||
proxyPort,
|
||||
ssl: true
|
||||
})
|
||||
.returning();
|
||||
@@ -135,18 +216,6 @@ export async function createResource(
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
if (
|
||||
error instanceof SqliteError &&
|
||||
error.code === "SQLITE_CONSTRAINT_UNIQUE"
|
||||
) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.CONFLICT,
|
||||
"Resource with that subdomain already exists"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
logger.error(error);
|
||||
return next(
|
||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||
|
||||
@@ -103,7 +103,7 @@ export async function deleteResource(
|
||||
.where(eq(newts.siteId, site.siteId))
|
||||
.limit(1);
|
||||
|
||||
removeTargets(newt.newtId, targetsToBeRemoved);
|
||||
removeTargets(newt.newtId, targetsToBeRemoved, deletedResource.protocol);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
109
server/routers/resource/getExchangeToken.ts
Normal file
109
server/routers/resource/getExchangeToken.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { db } from "@server/db";
|
||||
import { resources } from "@server/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { createResourceSession } from "@server/auth/sessions/resource";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import createHttpError from "http-errors";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import logger from "@server/logger";
|
||||
import { generateSessionToken } from "@server/auth/sessions/app";
|
||||
import config from "@server/lib/config";
|
||||
import {
|
||||
encodeHexLowerCase
|
||||
} from "@oslojs/encoding";
|
||||
import { sha256 } from "@oslojs/crypto/sha2";
|
||||
import { response } from "@server/lib";
|
||||
|
||||
const getExchangeTokenParams = z
|
||||
.object({
|
||||
resourceId: z
|
||||
.string()
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().positive())
|
||||
})
|
||||
.strict();
|
||||
|
||||
export type GetExchangeTokenResponse = {
|
||||
requestToken: string;
|
||||
};
|
||||
|
||||
export async function getExchangeToken(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<any> {
|
||||
try {
|
||||
const parsedParams = getExchangeTokenParams.safeParse(req.params);
|
||||
if (!parsedParams.success) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
fromError(parsedParams.error).toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const { resourceId } = parsedParams.data;
|
||||
|
||||
const resource = await db
|
||||
.select()
|
||||
.from(resources)
|
||||
.where(eq(resources.resourceId, resourceId))
|
||||
.limit(1);
|
||||
|
||||
if (resource.length === 0) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.NOT_FOUND,
|
||||
`Resource with ID ${resourceId} not found`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const ssoSession =
|
||||
req.cookies[config.getRawConfig().server.session_cookie_name];
|
||||
if (!ssoSession) {
|
||||
logger.debug(ssoSession);
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.UNAUTHORIZED,
|
||||
"Missing SSO session cookie"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const sessionId = encodeHexLowerCase(
|
||||
sha256(new TextEncoder().encode(ssoSession))
|
||||
);
|
||||
|
||||
const token = generateSessionToken();
|
||||
await createResourceSession({
|
||||
resourceId,
|
||||
token,
|
||||
userSessionId: sessionId,
|
||||
isRequestToken: true,
|
||||
expiresAt: Date.now() + 1000 * 30, // 30 seconds
|
||||
sessionLength: 1000 * 30,
|
||||
doNotExtend: true
|
||||
});
|
||||
|
||||
logger.debug("Request token created successfully");
|
||||
|
||||
return response<GetExchangeTokenResponse>(res, {
|
||||
data: {
|
||||
requestToken: token
|
||||
},
|
||||
success: true,
|
||||
error: false,
|
||||
message: "Request token created successfully",
|
||||
status: HttpCode.OK
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
return next(
|
||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -17,3 +17,4 @@ export * from "./getResourceWhitelist";
|
||||
export * from "./authWithWhitelist";
|
||||
export * from "./authWithAccessToken";
|
||||
export * from "./transferResource";
|
||||
export * from "./getExchangeToken";
|
||||
|
||||
@@ -63,7 +63,10 @@ function queryResources(
|
||||
passwordId: resourcePassword.passwordId,
|
||||
pincodeId: resourcePincode.pincodeId,
|
||||
sso: resources.sso,
|
||||
whitelist: resources.emailWhitelistEnabled
|
||||
whitelist: resources.emailWhitelistEnabled,
|
||||
http: resources.http,
|
||||
protocol: resources.protocol,
|
||||
proxyPort: resources.proxyPort
|
||||
})
|
||||
.from(resources)
|
||||
.leftJoin(sites, eq(resources.siteId, sites.siteId))
|
||||
@@ -93,7 +96,10 @@ function queryResources(
|
||||
passwordId: resourcePassword.passwordId,
|
||||
sso: resources.sso,
|
||||
pincodeId: resourcePincode.pincodeId,
|
||||
whitelist: resources.emailWhitelistEnabled
|
||||
whitelist: resources.emailWhitelistEnabled,
|
||||
http: resources.http,
|
||||
protocol: resources.protocol,
|
||||
proxyPort: resources.proxyPort
|
||||
})
|
||||
.from(resources)
|
||||
.leftJoin(sites, eq(resources.siteId, sites.siteId))
|
||||
|
||||
@@ -11,7 +11,20 @@ import { and, eq } from "drizzle-orm";
|
||||
|
||||
const setResourceWhitelistBodySchema = z
|
||||
.object({
|
||||
emails: z.array(z.string().email()).max(50)
|
||||
emails: z
|
||||
.array(
|
||||
z
|
||||
.string()
|
||||
.email()
|
||||
.or(
|
||||
z.string().regex(/^\*@[\w.-]+\.[a-zA-Z]{2,}$/, {
|
||||
message:
|
||||
"Invalid email address. Wildcard (*) must be the entire local part."
|
||||
})
|
||||
)
|
||||
)
|
||||
.max(50)
|
||||
.transform((v) => v.map((e) => e.toLowerCase()))
|
||||
})
|
||||
.strict();
|
||||
|
||||
|
||||
@@ -26,8 +26,8 @@ const updateResourceBodySchema = z
|
||||
ssl: z.boolean().optional(),
|
||||
sso: z.boolean().optional(),
|
||||
blockAccess: z.boolean().optional(),
|
||||
proxyPort: z.number().int().min(1).max(65535).optional(),
|
||||
emailWhitelistEnabled: z.boolean().optional()
|
||||
// siteId: z.number(),
|
||||
})
|
||||
.strict()
|
||||
.refine((data) => Object.keys(data).length > 0, {
|
||||
@@ -111,6 +111,10 @@ export async function updateResource(
|
||||
);
|
||||
}
|
||||
|
||||
if (resource[0].resources.ssl !== updatedResource[0].ssl) {
|
||||
// invalidate all sessions?
|
||||
}
|
||||
|
||||
return response(res, {
|
||||
data: updatedResource[0],
|
||||
success: true,
|
||||
|
||||
Reference in New Issue
Block a user