fix issues from test deploy

This commit is contained in:
Milo Schwartz 2024-12-21 21:01:12 -05:00
parent 3fb3be1f1e
commit ce5df3b0b9
No known key found for this signature in database
92 changed files with 1410 additions and 1019 deletions

View file

@ -38,9 +38,9 @@ export function createApiServer() {
if (!dev) {
apiServer.use(
rateLimitMiddleware({
windowMin: config.rate_limit.window_minutes,
max: config.rate_limit.max_requests,
type: "IP_ONLY",
windowMin: config.rate_limits.global.window_minutes,
max: config.rate_limits.global.max_requests,
type: "IP_AND_PATH",
}),
);
}

View file

@ -16,7 +16,7 @@ const environmentSchema = z.object({
app: z.object({
base_url: z.string().url(),
log_level: z.enum(["debug", "info", "warn", "error"]),
save_logs: z.boolean(),
save_logs: z.boolean()
}),
server: z.object({
external_port: portSchema,
@ -26,24 +26,32 @@ const environmentSchema = z.object({
secure_cookies: z.boolean(),
signup_secret: z.string().optional(),
session_cookie_name: z.string(),
resource_session_cookie_name: z.string(),
resource_session_cookie_name: z.string()
}),
traefik: z.object({
http_entrypoint: z.string(),
https_entrypoint: z.string().optional(),
cert_resolver: z.string().optional(),
prefer_wildcard_cert: z.boolean().optional(),
prefer_wildcard_cert: z.boolean().optional()
}),
gerbil: z.object({
start_port: portSchema,
base_endpoint: z.string(),
use_subdomain: z.boolean(),
subnet_group: z.string(),
block_size: z.number().positive().gt(0),
block_size: z.number().positive().gt(0)
}),
rate_limit: z.object({
window_minutes: z.number().positive().gt(0),
max_requests: z.number().positive().gt(0),
rate_limits: z.object({
global: z.object({
window_minutes: z.number().positive().gt(0),
max_requests: z.number().positive().gt(0)
}),
auth: z
.object({
window_minutes: z.number().positive().gt(0),
max_requests: z.number().positive().gt(0)
})
.optional()
}),
email: z
.object({
@ -51,7 +59,7 @@ const environmentSchema = z.object({
smtp_port: portSchema.optional(),
smtp_user: z.string().optional(),
smtp_pass: z.string().optional(),
no_reply: z.string().email().optional(),
no_reply: z.string().email().optional()
})
.optional(),
flags: z
@ -59,9 +67,9 @@ const environmentSchema = z.object({
allow_org_subdomain_changing: z.boolean().optional(),
require_email_verification: z.boolean().optional(),
disable_signup_without_invite: z.boolean().optional(),
require_signup_secret: z.boolean().optional(),
require_signup_secret: z.boolean().optional()
})
.optional(),
.optional()
});
const loadConfig = (configPath: string) => {
@ -72,7 +80,7 @@ const loadConfig = (configPath: string) => {
} catch (error) {
if (error instanceof Error) {
throw new Error(
`Error loading configuration file: ${error.message}`,
`Error loading configuration file: ${error.message}`
);
}
throw error;
@ -94,21 +102,21 @@ if (!environment) {
try {
const exampleConfigContent = fs.readFileSync(
exampleConfigPath,
"utf8",
"utf8"
);
fs.writeFileSync(configFilePath1, exampleConfigContent, "utf8");
environment = loadConfig(configFilePath1);
} catch (error) {
if (error instanceof Error) {
throw new Error(
`Error creating configuration file from example: ${error.message}`,
`Error creating configuration file from example: ${error.message}`
);
}
throw error;
}
} else {
throw new Error(
"No configuration file found and no example configuration available",
"No configuration file found and no example configuration available"
);
}
}

View file

@ -9,9 +9,11 @@ import { resourceAccessToken } from "@server/db/schema";
import { and, eq } from "drizzle-orm";
import db from "@server/db";
const deleteAccessTokenParamsSchema = z.object({
accessTokenId: z.string()
});
const deleteAccessTokenParamsSchema = z
.object({
accessTokenId: z.string()
})
.strict();
export async function deleteAccessToken(
req: Request,

View file

@ -5,7 +5,11 @@ import {
SESSION_COOKIE_EXPIRES
} from "@server/auth";
import db from "@server/db";
import { ResourceAccessToken, resourceAccessToken, resources } from "@server/db/schema";
import {
ResourceAccessToken,
resourceAccessToken,
resources
} from "@server/db/schema";
import HttpCode from "@server/types/HttpCode";
import response from "@server/utils/response";
import { eq } from "drizzle-orm";
@ -16,17 +20,27 @@ import { fromError } from "zod-validation-error";
import logger from "@server/logger";
import { createDate, TimeSpan } from "oslo";
export const generateAccessTokenBodySchema = z.object({
validForSeconds: z.number().int().positive().optional(), // seconds
title: z.string().optional(),
description: z.string().optional()
});
export const generateAccessTokenBodySchema = z
.object({
validForSeconds: z.number().int().positive().optional(), // seconds
title: z.string().optional(),
description: z.string().optional()
})
.strict();
export const generateAccssTokenParamsSchema = z.object({
resourceId: z.string().transform(Number).pipe(z.number().int().positive())
});
export const generateAccssTokenParamsSchema = z
.object({
resourceId: z
.string()
.transform(Number)
.pipe(z.number().int().positive())
})
.strict();
export type GenerateAccessTokenResponse = ResourceAccessToken;
export type GenerateAccessTokenResponse = Omit<
ResourceAccessToken,
"tokenHash"
> & { accessToken: string };
export async function generateAccessToken(
req: Request,
@ -77,25 +91,38 @@ export async function generateAccessToken(
const token = generateIdFromEntropySize(25);
// const tokenHash = await hash(token, {
// memoryCost: 19456,
// timeCost: 2,
// outputLen: 32,
// parallelism: 1
// });
const tokenHash = await hash(token, {
memoryCost: 19456,
timeCost: 2,
outputLen: 32,
parallelism: 1
});
const id = generateId(15);
const [result] = await db.insert(resourceAccessToken).values({
accessTokenId: id,
orgId: resource.orgId,
resourceId,
tokenHash: token,
expiresAt: expiresAt || null,
sessionLength: sessionLength,
title: title || null,
description: description || null,
createdAt: new Date().getTime()
}).returning();
const [result] = await db
.insert(resourceAccessToken)
.values({
accessTokenId: id,
orgId: resource.orgId,
resourceId,
tokenHash,
expiresAt: expiresAt || null,
sessionLength: sessionLength,
title: title || null,
description: description || null,
createdAt: new Date().getTime()
})
.returning({
accessTokenId: resourceAccessToken.accessTokenId,
orgId: resourceAccessToken.orgId,
resourceId: resourceAccessToken.resourceId,
expiresAt: resourceAccessToken.expiresAt,
sessionLength: resourceAccessToken.sessionLength,
title: resourceAccessToken.title,
description: resourceAccessToken.description,
createdAt: resourceAccessToken.createdAt
})
.execute();
if (!result) {
return next(
@ -107,7 +134,7 @@ export async function generateAccessToken(
}
return response<GenerateAccessTokenResponse>(res, {
data: result,
data: { ...result, accessToken: token },
success: true,
error: false,
message: "Resource access token generated successfully",

View file

@ -23,6 +23,7 @@ const listAccessTokensParamsSchema = z
.pipe(z.number().int().positive().optional()),
orgId: z.string().optional()
})
.strict()
.refine((data) => !!data.resourceId !== !!data.orgId, {
message: "Either resourceId or orgId must be provided, but not both"
});
@ -65,7 +66,10 @@ function queryAccessTokens(
return db
.select(cols)
.from(resourceAccessToken)
.leftJoin(resources, eq(resourceAccessToken.resourceId, resources.resourceId))
.leftJoin(
resources,
eq(resourceAccessToken.resourceId, resources.resourceId)
)
.where(
and(
inArray(
@ -83,7 +87,10 @@ function queryAccessTokens(
return db
.select(cols)
.from(resourceAccessToken)
.leftJoin(resources, eq(resourceAccessToken.resourceId, resources.resourceId))
.leftJoin(
resources,
eq(resourceAccessToken.resourceId, resources.resourceId)
)
.where(
and(
inArray(

View file

@ -11,12 +11,13 @@ import { response } from "@server/utils";
import { hashPassword, verifyPassword } from "@server/auth/password";
import { verifyTotpCode } from "@server/auth/2fa";
import { passwordSchema } from "@server/auth/passwordSchema";
import logger from "@server/logger";
export const changePasswordBody = z.object({
oldPassword: z.string(),
newPassword: passwordSchema,
code: z.string().optional(),
});
}).strict();
export type ChangePasswordBody = z.infer<typeof changePasswordBody>;
@ -108,6 +109,7 @@ export async function changePassword(
status: HttpCode.OK,
});
} catch (error) {
logger.error(error);
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,

View file

@ -5,11 +5,12 @@ import { fromError } from "zod-validation-error";
import HttpCode from "@server/types/HttpCode";
import { response } from "@server/utils";
import { validateResourceSessionToken } from "@server/auth/resource";
import logger from "@server/logger";
export const params = z.object({
token: z.string(),
resourceId: z.string().transform(Number).pipe(z.number().int().positive()),
});
}).strict();
export type CheckResourceSessionParams = z.infer<typeof params>;
@ -54,6 +55,7 @@ export async function checkResourceSession(
status: HttpCode.OK,
});
} catch (e) {
logger.error(e);
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,

View file

@ -10,11 +10,12 @@ import { eq } from "drizzle-orm";
import { response } from "@server/utils";
import { verifyPassword } from "@server/auth/password";
import { verifyTotpCode } from "@server/auth/2fa";
import logger from "@server/logger";
export const disable2faBody = z.object({
password: z.string(),
code: z.string().optional(),
});
}).strict();
export type Disable2faBody = z.infer<typeof disable2faBody>;
@ -100,6 +101,7 @@ export async function disable2fa(
status: HttpCode.OK,
});
} catch (error) {
logger.error(error);
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,

View file

@ -22,7 +22,7 @@ export const loginBodySchema = z.object({
email: z.string().email(),
password: z.string(),
code: z.string().optional(),
});
}).strict();
export type LoginBody = z.infer<typeof loginBodySchema>;
@ -151,6 +151,7 @@ export async function login(
status: HttpCode.OK,
});
} catch (e) {
logger.error(e);
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,

View file

@ -6,13 +6,13 @@ import logger from "@server/logger";
import {
createBlankSessionTokenCookie,
invalidateSession,
SESSION_COOKIE_NAME,
SESSION_COOKIE_NAME
} from "@server/auth";
export async function logout(
req: Request,
res: Response,
next: NextFunction,
next: NextFunction
): Promise<any> {
const sessionId = req.cookies[SESSION_COOKIE_NAME];
@ -20,8 +20,8 @@ export async function logout(
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"You must be logged in to sign out",
),
"You must be logged in to sign out"
)
);
}
@ -34,15 +34,12 @@ export async function logout(
success: true,
error: false,
message: "Logged out successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error("Failed to log out", error);
logger.error(error);
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"Failed to log out",
),
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "Failed to log out")
);
}
}

View file

@ -3,8 +3,9 @@ import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { response } from "@server/utils";
import { User } from "@server/db/schema";
import { sendEmailVerificationCode } from "./sendEmailVerificationCode";
import { sendEmailVerificationCode } from "../../auth/sendEmailVerificationCode";
import config from "@server/config";
import logger from "@server/logger";
export type RequestEmailVerificationCodeResponse = {
codeSent: boolean;
@ -40,14 +41,15 @@ export async function requestEmailVerificationCode(
return response<RequestEmailVerificationCodeResponse>(res, {
data: {
codeSent: true,
codeSent: true
},
status: HttpCode.OK,
success: true,
error: false,
message: `Email verification code sent to ${user.email}`,
message: `Email verification code sent to ${user.email}`
});
} catch (error) {
logger.error(error);
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,

View file

@ -16,7 +16,7 @@ import { TimeSpan } from "oslo";
export const requestPasswordResetBody = z.object({
email: z.string().email(),
});
}).strict();
export type RequestPasswordResetBody = z.infer<typeof requestPasswordResetBody>;
@ -87,6 +87,7 @@ export async function requestPasswordReset(
status: HttpCode.OK,
});
} catch (e) {
logger.error(e);
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,

View file

@ -12,10 +12,13 @@ import { eq } from "drizzle-orm";
import { verify } from "@node-rs/argon2";
import { createTOTPKeyURI } from "oslo/otp";
import config from "@server/config";
import logger from "@server/logger";
export const requestTotpSecretBody = z.object({
password: z.string(),
});
export const requestTotpSecretBody = z
.object({
password: z.string()
})
.strict();
export type RequestTotpSecretBody = z.infer<typeof requestTotpSecretBody>;
@ -26,7 +29,7 @@ export type RequestTotpSecretResponse = {
export async function requestTotpSecret(
req: Request,
res: Response,
next: NextFunction,
next: NextFunction
): Promise<any> {
const parsedBody = requestTotpSecretBody.safeParse(req.body);
@ -34,8 +37,8 @@ export async function requestTotpSecret(
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedBody.error).toString(),
),
fromError(parsedBody.error).toString()
)
);
}
@ -48,7 +51,7 @@ export async function requestTotpSecret(
memoryCost: 19456,
timeCost: 2,
outputLen: 32,
parallelism: 1,
parallelism: 1
});
if (!validPassword) {
return next(unauthorized());
@ -58,8 +61,8 @@ export async function requestTotpSecret(
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"User has already enabled two-factor authentication",
),
"User has already enabled two-factor authentication"
)
);
}
@ -70,25 +73,26 @@ export async function requestTotpSecret(
await db
.update(users)
.set({
twoFactorSecret: secret,
twoFactorSecret: secret
})
.where(eq(users.userId, user.userId));
return response<RequestTotpSecretResponse>(res, {
data: {
secret: uri,
secret: uri
},
success: true,
error: false,
message: "TOTP secret generated successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"Failed to generate TOTP secret",
),
"Failed to generate TOTP secret"
)
);
}
}

View file

@ -14,12 +14,15 @@ import { passwordSchema } from "@server/auth/passwordSchema";
import { encodeHex } from "oslo/encoding";
import { isWithinExpirationDate } from "oslo";
import { invalidateAllSessions } from "@server/auth";
import logger from "@server/logger";
export const resetPasswordBody = z.object({
token: z.string(),
newPassword: passwordSchema,
code: z.string().optional(),
});
export const resetPasswordBody = z
.object({
token: z.string(),
newPassword: passwordSchema,
code: z.string().optional()
})
.strict();
export type ResetPasswordBody = z.infer<typeof resetPasswordBody>;
@ -30,7 +33,7 @@ export type ResetPasswordResponse = {
export async function resetPassword(
req: Request,
res: Response,
next: NextFunction,
next: NextFunction
): Promise<any> {
const parsedBody = resetPasswordBody.safeParse(req.body);
@ -38,8 +41,8 @@ export async function resetPassword(
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedBody.error).toString(),
),
fromError(parsedBody.error).toString()
)
);
}
@ -47,7 +50,7 @@ export async function resetPassword(
try {
const tokenHash = encodeHex(
await sha256(new TextEncoder().encode(token)),
await sha256(new TextEncoder().encode(token))
);
const resetRequest = await db
@ -63,8 +66,8 @@ export async function resetPassword(
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"Invalid or expired password reset token",
),
"Invalid or expired password reset token"
)
);
}
@ -77,8 +80,8 @@ export async function resetPassword(
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"User not found",
),
"User not found"
)
);
}
@ -89,22 +92,22 @@ export async function resetPassword(
success: true,
error: false,
message: "Two-factor authentication required",
status: HttpCode.ACCEPTED,
status: HttpCode.ACCEPTED
});
}
const validOTP = await verifyTotpCode(
code!,
user[0].twoFactorSecret!,
user[0].userId,
user[0].userId
);
if (!validOTP) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"Invalid two-factor authentication code",
),
"Invalid two-factor authentication code"
)
);
}
}
@ -129,14 +132,15 @@ export async function resetPassword(
success: true,
error: false,
message: "Password reset successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (e) {
logger.error(e);
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"Failed to reset password",
),
"Failed to reset password"
)
);
}
}

View file

@ -8,7 +8,7 @@ import { fromError } from "zod-validation-error";
import createHttpError from "http-errors";
import response from "@server/utils/response";
import { SqliteError } from "better-sqlite3";
import { sendEmailVerificationCode } from "./sendEmailVerificationCode";
import { sendEmailVerificationCode } from "../../auth/sendEmailVerificationCode";
import { passwordSchema } from "@server/auth/passwordSchema";
import { eq } from "drizzle-orm";
import moment from "moment";
@ -20,6 +20,7 @@ import {
} from "@server/auth";
import { ActionsEnum } from "@server/auth/actions";
import config from "@server/config";
import logger from "@server/logger";
export const signupBodySchema = z.object({
email: z.string().email(),
@ -153,6 +154,7 @@ export async function signup(
)
);
} else {
logger.error(e);
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,

View file

@ -9,10 +9,13 @@ import { User, emailVerificationCodes, users } from "@server/db/schema";
import { eq } from "drizzle-orm";
import { isWithinExpirationDate } from "oslo";
import config from "@server/config";
import logger from "@server/logger";
export const verifyEmailBody = z.object({
code: z.string(),
});
export const verifyEmailBody = z
.object({
code: z.string()
})
.strict();
export type VerifyEmailBody = z.infer<typeof verifyEmailBody>;
@ -66,7 +69,7 @@ export async function verifyEmail(
await db
.update(users)
.set({
emailVerified: true,
emailVerified: true
})
.where(eq(users.userId, user.userId));
} else {
@ -84,10 +87,11 @@ export async function verifyEmail(
message: "Email verified",
status: HttpCode.OK,
data: {
valid,
},
valid
}
});
} catch (error) {
logger.error(error);
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,

View file

@ -10,10 +10,13 @@ import { eq } from "drizzle-orm";
import { alphabet, generateRandomString } from "oslo/crypto";
import { hashPassword } from "@server/auth/password";
import { verifyTotpCode } from "@server/auth/2fa";
import logger from "@server/logger";
export const verifyTotpBody = z.object({
code: z.string(),
});
export const verifyTotpBody = z
.object({
code: z.string()
})
.strict();
export type VerifyTotpBody = z.infer<typeof verifyTotpBody>;
@ -82,7 +85,7 @@ export async function verifyTotp(
await db.insert(twoFactorBackupCodes).values({
userId: user.userId,
codeHash: hash,
codeHash: hash
});
}
}
@ -92,16 +95,17 @@ export async function verifyTotp(
return response<VerifyTotpResponse>(res, {
data: {
valid,
...(valid && codes ? { backupCodes: codes } : {}),
...(valid && codes ? { backupCodes: codes } : {})
},
success: true,
error: false,
message: valid
? "Code is valid. Two-factor is now enabled"
: "Code is invalid",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,

View file

@ -139,39 +139,36 @@ export async function verifyResourceSession(
);
if (resourceSession) {
return allowed(res);
if (pincode && resourceSession.pincodeId) {
logger.debug(
"Resource allowed because pincode session is valid"
);
return allowed(res);
}
// Might not be needed
// if (pincode && resourceSession.pincodeId) {
// logger.debug(
// "Resource allowed because pincode session is valid"
// );
// return allowed(res);
// }
//
// if (password && resourceSession.passwordId) {
// logger.debug(
// "Resource allowed because password session is valid"
// );
// return allowed(res);
// }
//
// if (
// resource.emailWhitelistEnabled &&
// resourceSession.whitelistId
// ) {
// logger.debug(
// "Resource allowed because whitelist session is valid"
// );
// return allowed(res);
// }
//
// if (resourceSession.accessTokenId) {
// logger.debug(
// "Resource allowed because access token session is valid"
// );
// return allowed(res);
// }
if (password && resourceSession.passwordId) {
logger.debug(
"Resource allowed because password session is valid"
);
return allowed(res);
}
if (
resource.emailWhitelistEnabled &&
resourceSession.whitelistId
) {
logger.debug(
"Resource allowed because whitelist session is valid"
);
return allowed(res);
}
if (resourceSession.accessTokenId) {
logger.debug(
"Resource allowed because access token session is valid"
);
return allowed(res);
}
}
}

View file

@ -1,4 +1,5 @@
import { Router } from "express";
import config from "@server/config";
import * as site from "./site";
import * as org from "./org";
import * as resource from "./resource";
@ -419,8 +420,12 @@ export const authRouter = Router();
unauthenticated.use("/auth", authRouter);
authRouter.use(
rateLimitMiddleware({
windowMin: 10,
max: 75,
windowMin:
config.rate_limits.auth?.window_minutes ||
config.rate_limits.global.window_minutes,
max:
config.rate_limits.auth?.max_requests ||
config.rate_limits.global.max_requests,
type: "IP_AND_PATH"
})
);

View file

@ -9,9 +9,11 @@ import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
const getOrgSchema = z.object({
orgId: z.string(),
});
const getOrgSchema = z
.object({
orgId: z.string()
})
.strict();
export async function checkId(
req: Request,
@ -43,7 +45,7 @@ export async function checkId(
success: true,
error: false,
message: "Organization ID already exists",
status: HttpCode.OK,
status: HttpCode.OK
});
}
@ -52,7 +54,7 @@ export async function checkId(
success: true,
error: false,
message: "Organization ID is available",
status: HttpCode.NOT_FOUND,
status: HttpCode.NOT_FOUND
});
} catch (error) {
logger.error(error);

View file

@ -10,9 +10,11 @@ import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
const deleteOrgSchema = z.object({
orgId: z.string(),
});
const deleteOrgSchema = z
.object({
orgId: z.string()
})
.strict();
export async function deleteOrg(
req: Request,
@ -65,7 +67,7 @@ export async function deleteOrg(
success: true,
error: false,
message: "Organization deleted successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);

View file

@ -8,9 +8,11 @@ import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import logger from "@server/logger";
const getOrgSchema = z.object({
orgId: z.string(),
});
const getOrgSchema = z
.object({
orgId: z.string()
})
.strict();
export type GetOrgResponse = {
org: Org;
@ -51,12 +53,12 @@ export async function getOrg(
return response<GetOrgResponse>(res, {
data: {
org: org[0],
org: org[0]
},
success: true,
error: false,
message: "Organization retrieved successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);

View file

@ -9,18 +9,20 @@ import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
const updateOrgParamsSchema = z.object({
orgId: z.string(),
});
const updateOrgParamsSchema = z
.object({
orgId: z.string()
})
.strict();
const updateOrgBodySchema = z
.object({
name: z.string().min(1).max(255).optional(),
domain: z.string().min(1).max(255).optional(),
domain: z.string().min(1).max(255).optional()
})
.strict()
.refine((data) => Object.keys(data).length > 0, {
message: "At least one field must be provided for update",
message: "At least one field must be provided for update"
});
export async function updateOrg(
@ -72,7 +74,7 @@ export async function updateOrg(
success: true,
error: false,
message: "Organization updated successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);

View file

@ -17,13 +17,21 @@ import logger from "@server/logger";
import { verify } from "@node-rs/argon2";
import { isWithinExpirationDate } from "oslo";
const authWithAccessTokenBodySchema = z.object({
accessToken: z.string()
});
const authWithAccessTokenBodySchema = z
.object({
accessToken: z.string(),
accessTokenId: z.string()
})
.strict();
const authWithAccessTokenParamsSchema = z.object({
resourceId: z.string().transform(Number).pipe(z.number().int().positive())
});
const authWithAccessTokenParamsSchema = z
.object({
resourceId: z
.string()
.transform(Number)
.pipe(z.number().int().positive())
})
.strict();
export type AuthWithAccessTokenResponse = {
session?: string;
@ -57,9 +65,7 @@ export async function authWithAccessToken(
}
const { resourceId } = parsedParams.data;
const { accessToken: at } = parsedBody.data;
const [accessTokenId, accessToken] = at.split(".");
const { accessToken, accessTokenId } = parsedBody.data;
try {
const [result] = await db
@ -86,7 +92,7 @@ export async function authWithAccessToken(
HttpCode.UNAUTHORIZED,
createHttpError(
HttpCode.BAD_REQUEST,
"Email is not whitelisted"
"Access token does not exist for resource"
)
)
);
@ -98,15 +104,12 @@ export async function authWithAccessToken(
);
}
// const validCode = await verify(tokenItem.tokenHash, accessToken, {
// memoryCost: 19456,
// timeCost: 2,
// outputLen: 32,
// parallelism: 1
// });
logger.debug(`${accessToken} ${tokenItem.tokenHash}`)
const validCode = accessToken === tokenItem.tokenHash;
const validCode = await verify(tokenItem.tokenHash, accessToken, {
memoryCost: 19456,
timeCost: 2,
outputLen: 32,
parallelism: 1
});
if (!validCode) {
return next(
createHttpError(HttpCode.UNAUTHORIZED, "Invalid access token")

View file

@ -14,14 +14,22 @@ import {
serializeResourceSessionCookie
} from "@server/auth/resource";
import config from "@server/config";
import logger from "@server/logger";
export const authWithPasswordBodySchema = z.object({
password: z.string()
});
export const authWithPasswordBodySchema = z
.object({
password: z.string()
})
.strict();
export const authWithPasswordParamsSchema = z.object({
resourceId: z.string().transform(Number).pipe(z.number().int().positive())
});
export const authWithPasswordParamsSchema = z
.object({
resourceId: z
.string()
.transform(Number)
.pipe(z.number().int().positive())
})
.strict();
export type AuthWithPasswordResponse = {
session?: string;
@ -120,10 +128,7 @@ export async function authWithPassword(
passwordId: definedPassword.passwordId
});
const cookieName = `${config.server.resource_session_cookie_name}_${resource.resourceId}`;
const cookie = serializeResourceSessionCookie(
cookieName,
token,
);
const cookie = serializeResourceSessionCookie(cookieName, token);
res.appendHeader("Set-Cookie", cookie);
return response<AuthWithPasswordResponse>(res, {
@ -136,6 +141,7 @@ export async function authWithPassword(
status: HttpCode.OK
});
} catch (e) {
logger.error(e);
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,

View file

@ -24,13 +24,20 @@ import config from "@server/config";
import { AuthWithPasswordResponse } from "./authWithPassword";
import { isValidOtp, sendResourceOtpEmail } from "@server/auth/resourceOtp";
export const authWithPincodeBodySchema = z.object({
pincode: z.string()
});
export const authWithPincodeBodySchema = z
.object({
pincode: z.string()
})
.strict();
export const authWithPincodeParamsSchema = z.object({
resourceId: z.string().transform(Number).pipe(z.number().int().positive())
});
export const authWithPincodeParamsSchema = z
.object({
resourceId: z
.string()
.transform(Number)
.pipe(z.number().int().positive())
})
.strict();
export type AuthWithPincodeResponse = {
session?: string;
@ -128,10 +135,7 @@ export async function authWithPincode(
pincodeId: definedPincode.pincodeId
});
const cookieName = `${config.server.resource_session_cookie_name}_${resource.resourceId}`;
const cookie = serializeResourceSessionCookie(
cookieName,
token,
);
const cookie = serializeResourceSessionCookie(cookieName, token);
res.appendHeader("Set-Cookie", cookie);
return response<AuthWithPincodeResponse>(res, {

View file

@ -22,14 +22,21 @@ import config from "@server/config";
import { isValidOtp, sendResourceOtpEmail } from "@server/auth/resourceOtp";
import logger from "@server/logger";
const authWithWhitelistBodySchema = z.object({
email: z.string().email(),
otp: z.string().optional()
});
const authWithWhitelistBodySchema = z
.object({
email: z.string().email(),
otp: z.string().optional()
})
.strict();
const authWithWhitelistParamsSchema = z.object({
resourceId: z.string().transform(Number).pipe(z.number().int().positive())
});
const authWithWhitelistParamsSchema = z
.object({
resourceId: z
.string()
.transform(Number)
.pipe(z.number().int().positive())
})
.strict();
export type AuthWithWhitelistResponse = {
otpSent?: boolean;
@ -171,10 +178,7 @@ export async function authWithWhitelist(
whitelistId: whitelistedEmail.whitelistId
});
const cookieName = `${config.server.resource_session_cookie_name}_${resource.resourceId}`;
const cookie = serializeResourceSessionCookie(
cookieName,
token,
);
const cookie = serializeResourceSessionCookie(cookieName, token);
res.appendHeader("Set-Cookie", cookie);
return response<AuthWithWhitelistResponse>(res, {

View file

@ -1,3 +1,4 @@
import { SqliteError } from "better-sqlite3";
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { db } from "@server/db";
@ -7,7 +8,7 @@ import {
resources,
roleResources,
roles,
userResources,
userResources
} from "@server/db/schema";
import response from "@server/utils/response";
import HttpCode from "@server/types/HttpCode";
@ -16,16 +17,19 @@ import { eq, and } from "drizzle-orm";
import stoi from "@server/utils/stoi";
import { fromError } from "zod-validation-error";
import { subdomainSchema } from "@server/schemas/subdomainSchema";
import logger from "@server/logger";
const createResourceParamsSchema = z.object({
siteId: z.string().transform(stoi).pipe(z.number().int().positive()),
orgId: z.string(),
});
const createResourceParamsSchema = z
.object({
siteId: z.string().transform(stoi).pipe(z.number().int().positive()),
orgId: z.string()
})
.strict();
const createResourceSchema = z
.object({
name: z.string().min(1).max(255),
subdomain: subdomainSchema,
subdomain: subdomainSchema
})
.strict();
@ -94,7 +98,7 @@ export async function createResource(
orgId,
name,
subdomain,
ssl: true,
ssl: true
})
.returning();
@ -112,14 +116,14 @@ export async function createResource(
await db.insert(roleResources).values({
roleId: adminRole[0].roleId,
resourceId: newResource[0].resourceId,
resourceId: newResource[0].resourceId
});
if (req.userOrgRoleId != adminRole[0].roleId) {
// make sure the user can access the resource
await db.insert(userResources).values({
userId: req.user?.userId!,
resourceId: newResource[0].resourceId,
resourceId: newResource[0].resourceId
});
}
@ -128,9 +132,22 @@ export async function createResource(
success: true,
error: false,
message: "Resource created successfully",
status: HttpCode.CREATED,
status: HttpCode.CREATED
});
} 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")
);

View file

@ -12,9 +12,14 @@ import { addPeer } from "../gerbil/peers";
import { removeTargets } from "../newt/targets";
// Define Zod schema for request parameters validation
const deleteResourceSchema = z.object({
resourceId: z.string().transform(Number).pipe(z.number().int().positive()),
});
const deleteResourceSchema = z
.object({
resourceId: z
.string()
.transform(Number)
.pipe(z.number().int().positive())
})
.strict();
export async function deleteResource(
req: Request,
@ -67,20 +72,20 @@ export async function deleteResource(
)
);
}
if (site.pubKey) {
if (site.type == "wireguard") {
// TODO: is this all inefficient?
// Fetch resources for this site
const resourcesRes = await db.query.resources.findMany({
where: eq(resources.siteId, site.siteId),
where: eq(resources.siteId, site.siteId)
});
// Fetch targets for all resources of this site
const targetIps = await Promise.all(
resourcesRes.map(async (resource) => {
const targetsRes = await db.query.targets.findMany({
where: eq(targets.resourceId, resource.resourceId),
where: eq(targets.resourceId, resource.resourceId)
});
return targetsRes.map((target) => `${target.ip}/32`);
})
@ -88,7 +93,7 @@ export async function deleteResource(
await addPeer(site.exitNodeId!, {
publicKey: site.pubKey,
allowedIps: targetIps.flat(),
allowedIps: targetIps.flat()
});
} else if (site.type == "newt") {
// get the newt on the site by querying the newt table for siteId
@ -107,7 +112,7 @@ export async function deleteResource(
success: true,
error: false,
message: "Resource deleted successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);

View file

@ -7,10 +7,16 @@ import response from "@server/utils/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import { fromError } from "zod-validation-error";
import logger from "@server/logger";
const getResourceSchema = z.object({
resourceId: z.string().transform(Number).pipe(z.number().int().positive()),
});
const getResourceSchema = z
.object({
resourceId: z
.string()
.transform(Number)
.pipe(z.number().int().positive())
})
.strict();
export type GetResourceResponse = Resource;
@ -52,9 +58,10 @@ export async function getResource(
success: true,
error: false,
message: "Resource retrieved successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);
return next(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);

View file

@ -4,17 +4,23 @@ import { db } from "@server/db";
import {
resourcePassword,
resourcePincode,
resources,
resources
} from "@server/db/schema";
import { eq } from "drizzle-orm";
import response from "@server/utils/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import { fromError } from "zod-validation-error";
import logger from "@server/logger";
const getResourceAuthInfoSchema = z.object({
resourceId: z.string().transform(Number).pipe(z.number().int().positive()),
});
const getResourceAuthInfoSchema = z
.object({
resourceId: z
.string()
.transform(Number)
.pipe(z.number().int().positive())
})
.strict();
export type GetResourceAuthInfoResponse = {
resourceId: number;
@ -30,7 +36,7 @@ export type GetResourceAuthInfoResponse = {
export async function getResourceAuthInfo(
req: Request,
res: Response,
next: NextFunction,
next: NextFunction
): Promise<any> {
try {
const parsedParams = getResourceAuthInfoSchema.safeParse(req.params);
@ -38,8 +44,8 @@ export async function getResourceAuthInfo(
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedParams.error).toString(),
),
fromError(parsedParams.error).toString()
)
);
}
@ -50,11 +56,11 @@ export async function getResourceAuthInfo(
.from(resources)
.leftJoin(
resourcePincode,
eq(resourcePincode.resourceId, resources.resourceId),
eq(resourcePincode.resourceId, resources.resourceId)
)
.leftJoin(
resourcePassword,
eq(resourcePassword.resourceId, resources.resourceId),
eq(resourcePassword.resourceId, resources.resourceId)
)
.where(eq(resources.resourceId, resourceId))
.limit(1);
@ -67,7 +73,7 @@ export async function getResourceAuthInfo(
if (!resource) {
return next(
createHttpError(HttpCode.NOT_FOUND, "Resource not found"),
createHttpError(HttpCode.NOT_FOUND, "Resource not found")
);
}
@ -85,14 +91,12 @@ export async function getResourceAuthInfo(
success: true,
error: false,
message: "Resource auth info retrieved successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"An error occurred",
),
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}

View file

@ -9,9 +9,14 @@ import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
const getResourceWhitelistSchema = z.object({
resourceId: z.string().transform(Number).pipe(z.number().int().positive())
});
const getResourceWhitelistSchema = z
.object({
resourceId: z
.string()
.transform(Number)
.pipe(z.number().int().positive())
})
.strict();
async function queryWhitelist(resourceId: number) {
return await db

View file

@ -9,9 +9,14 @@ import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
const listResourceRolesSchema = z.object({
resourceId: z.string().transform(Number).pipe(z.number().int().positive()),
});
const listResourceRolesSchema = z
.object({
resourceId: z
.string()
.transform(Number)
.pipe(z.number().int().positive())
})
.strict();
async function query(resourceId: number) {
return await db
@ -19,7 +24,7 @@ async function query(resourceId: number) {
roleId: roles.roleId,
name: roles.name,
description: roles.description,
isAdmin: roles.isAdmin,
isAdmin: roles.isAdmin
})
.from(roleResources)
.innerJoin(roles, eq(roleResources.roleId, roles.roleId))
@ -52,12 +57,12 @@ export async function listResourceRoles(
return response<ListResourceRolesResponse>(res, {
data: {
roles: resourceRolesList,
roles: resourceRolesList
},
success: true,
error: false,
message: "Resource roles retrieved successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);

View file

@ -9,15 +9,20 @@ import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
const listResourceUsersSchema = z.object({
resourceId: z.string().transform(Number).pipe(z.number().int().positive()),
});
const listResourceUsersSchema = z
.object({
resourceId: z
.string()
.transform(Number)
.pipe(z.number().int().positive())
})
.strict();
async function queryUsers(resourceId: number) {
return await db
.select({
userId: userResources.userId,
email: users.email,
email: users.email
})
.from(userResources)
.innerJoin(users, eq(userResources.userId, users.userId))
@ -50,12 +55,12 @@ export async function listResourceUsers(
return response<ListResourceUsersResponse>(res, {
data: {
users: resourceUsersList,
users: resourceUsersList
},
success: true,
error: false,
message: "Resource users retrieved successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);

View file

@ -7,7 +7,7 @@ import {
userResources,
roleResources,
resourcePassword,
resourcePincode,
resourcePincode
} from "@server/db/schema";
import response from "@server/utils/response";
import HttpCode from "@server/types/HttpCode";
@ -23,10 +23,11 @@ const listResourcesParamsSchema = z
.optional()
.transform(stoi)
.pipe(z.number().int().positive().optional()),
orgId: z.string().optional(),
orgId: z.string().optional()
})
.strict()
.refine((data) => !!data.siteId !== !!data.orgId, {
message: "Either siteId or orgId must be provided, but not both",
message: "Either siteId or orgId must be provided, but not both"
});
const listResourcesSchema = z.object({
@ -42,13 +43,13 @@ const listResourcesSchema = z.object({
.optional()
.default("0")
.transform(Number)
.pipe(z.number().int().nonnegative()),
.pipe(z.number().int().nonnegative())
});
function queryResources(
accessibleResourceIds: number[],
siteId?: number,
orgId?: string,
orgId?: string
) {
if (siteId) {
return db
@ -68,17 +69,17 @@ function queryResources(
.leftJoin(sites, eq(resources.siteId, sites.siteId))
.leftJoin(
resourcePassword,
eq(resourcePassword.resourceId, resources.resourceId),
eq(resourcePassword.resourceId, resources.resourceId)
)
.leftJoin(
resourcePincode,
eq(resourcePincode.resourceId, resources.resourceId),
eq(resourcePincode.resourceId, resources.resourceId)
)
.where(
and(
inArray(resources.resourceId, accessibleResourceIds),
eq(resources.siteId, siteId),
),
eq(resources.siteId, siteId)
)
);
} else if (orgId) {
return db
@ -98,17 +99,17 @@ function queryResources(
.leftJoin(sites, eq(resources.siteId, sites.siteId))
.leftJoin(
resourcePassword,
eq(resourcePassword.resourceId, resources.resourceId),
eq(resourcePassword.resourceId, resources.resourceId)
)
.leftJoin(
resourcePincode,
eq(resourcePincode.resourceId, resources.resourceId),
eq(resourcePincode.resourceId, resources.resourceId)
)
.where(
and(
inArray(resources.resourceId, accessibleResourceIds),
eq(resources.orgId, orgId),
),
eq(resources.orgId, orgId)
)
);
}
}
@ -121,7 +122,7 @@ export type ListResourcesResponse = {
export async function listResources(
req: Request,
res: Response,
next: NextFunction,
next: NextFunction
): Promise<any> {
try {
const parsedQuery = listResourcesSchema.safeParse(req.query);
@ -129,8 +130,8 @@ export async function listResources(
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedQuery.error.errors.map((e) => e.message).join(", "),
),
parsedQuery.error.errors.map((e) => e.message).join(", ")
)
);
}
const { limit, offset } = parsedQuery.data;
@ -140,8 +141,8 @@ export async function listResources(
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map((e) => e.message).join(", "),
),
parsedParams.error.errors.map((e) => e.message).join(", ")
)
);
}
const { siteId, orgId } = parsedParams.data;
@ -150,29 +151,29 @@ export async function listResources(
return next(
createHttpError(
HttpCode.FORBIDDEN,
"User does not have access to this organization",
),
"User does not have access to this organization"
)
);
}
const accessibleResources = await db
.select({
resourceId: sql<number>`COALESCE(${userResources.resourceId}, ${roleResources.resourceId})`,
resourceId: sql<number>`COALESCE(${userResources.resourceId}, ${roleResources.resourceId})`
})
.from(userResources)
.fullJoin(
roleResources,
eq(userResources.resourceId, roleResources.resourceId),
eq(userResources.resourceId, roleResources.resourceId)
)
.where(
or(
eq(userResources.userId, req.user!.userId),
eq(roleResources.roleId, req.userOrgRoleId!),
),
eq(roleResources.roleId, req.userOrgRoleId!)
)
);
const accessibleResourceIds = accessibleResources.map(
(resource) => resource.resourceId,
(resource) => resource.resourceId
);
let countQuery: any = db
@ -192,21 +193,18 @@ export async function listResources(
pagination: {
total: totalCount,
limit,
offset,
},
offset
}
},
success: true,
error: false,
message: "Resources retrieved successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"An error occurred",
),
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}

View file

@ -8,14 +8,15 @@ import createHttpError from "http-errors";
import { fromError } from "zod-validation-error";
import { hash } from "@node-rs/argon2";
import { response } from "@server/utils";
import logger from "@server/logger";
const setResourceAuthMethodsParamsSchema = z.object({
resourceId: z.string().transform(Number).pipe(z.number().int().positive()),
resourceId: z.string().transform(Number).pipe(z.number().int().positive())
});
const setResourceAuthMethodsBodySchema = z
.object({
password: z.string().min(4).max(100).nullable(),
password: z.string().min(4).max(100).nullable()
})
.strict();
@ -60,7 +61,7 @@ export async function setResourcePassword(
memoryCost: 19456,
timeCost: 2,
outputLen: 32,
parallelism: 1,
parallelism: 1
});
await trx
@ -74,9 +75,10 @@ export async function setResourcePassword(
success: true,
error: false,
message: "Resource password set successfully",
status: HttpCode.CREATED,
status: HttpCode.CREATED
});
} catch (error) {
logger.error(error);
return next(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);

View file

@ -9,6 +9,7 @@ import { fromError } from "zod-validation-error";
import { hash } from "@node-rs/argon2";
import { response } from "@server/utils";
import stoi from "@server/utils/stoi";
import logger from "@server/logger";
const setResourceAuthMethodsParamsSchema = z.object({
resourceId: z.string().transform(Number).pipe(z.number().int().positive()),
@ -81,6 +82,7 @@ export async function setResourcePincode(
status: HttpCode.CREATED,
});
} catch (error) {
logger.error(error);
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,

View file

@ -9,13 +9,20 @@ import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { eq, and, ne } from "drizzle-orm";
const setResourceRolesBodySchema = z.object({
roleIds: z.array(z.number().int().positive()),
});
const setResourceRolesBodySchema = z
.object({
roleIds: z.array(z.number().int().positive())
})
.strict();
const setResourceRolesParamsSchema = z.object({
resourceId: z.string().transform(Number).pipe(z.number().int().positive()),
});
const setResourceRolesParamsSchema = z
.object({
resourceId: z
.string()
.transform(Number)
.pipe(z.number().int().positive())
})
.strict();
export async function setResourceRoles(
req: Request,
@ -99,7 +106,7 @@ export async function setResourceRoles(
success: true,
error: false,
message: "Roles set for resource successfully",
status: HttpCode.CREATED,
status: HttpCode.CREATED
});
});
} catch (error) {

View file

@ -9,13 +9,20 @@ import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { eq } from "drizzle-orm";
const setUserResourcesBodySchema = z.object({
userIds: z.array(z.string()),
});
const setUserResourcesBodySchema = z
.object({
userIds: z.array(z.string())
})
.strict();
const setUserResourcesParamsSchema = z.object({
resourceId: z.string().transform(Number).pipe(z.number().int().positive()),
});
const setUserResourcesParamsSchema = z
.object({
resourceId: z
.string()
.transform(Number)
.pipe(z.number().int().positive())
})
.strict();
export async function setResourceUsers(
req: Request,
@ -66,7 +73,7 @@ export async function setResourceUsers(
success: true,
error: false,
message: "Users set for resource successfully",
status: HttpCode.CREATED,
status: HttpCode.CREATED
});
});
} catch (error) {

View file

@ -9,13 +9,20 @@ import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { and, eq } from "drizzle-orm";
const setResourceWhitelistBodySchema = z.object({
emails: z.array(z.string().email()).max(50)
});
const setResourceWhitelistBodySchema = z
.object({
emails: z.array(z.string().email()).max(50)
})
.strict();
const setResourceWhitelistParamsSchema = z.object({
resourceId: z.string().transform(Number).pipe(z.number().int().positive())
});
const setResourceWhitelistParamsSchema = z
.object({
resourceId: z
.string()
.transform(Number)
.pipe(z.number().int().positive())
})
.strict();
export async function setResourceWhitelist(
req: Request,

View file

@ -10,9 +10,14 @@ import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { subdomainSchema } from "@server/schemas/subdomainSchema";
const updateResourceParamsSchema = z.object({
resourceId: z.string().transform(Number).pipe(z.number().int().positive()),
});
const updateResourceParamsSchema = z
.object({
resourceId: z
.string()
.transform(Number)
.pipe(z.number().int().positive())
})
.strict();
const updateResourceBodySchema = z
.object({
@ -21,18 +26,18 @@ const updateResourceBodySchema = z
ssl: z.boolean().optional(),
sso: z.boolean().optional(),
blockAccess: z.boolean().optional(),
emailWhitelistEnabled: z.boolean().optional(),
emailWhitelistEnabled: z.boolean().optional()
// siteId: z.number(),
})
.strict()
.refine((data) => Object.keys(data).length > 0, {
message: "At least one field must be provided for update",
message: "At least one field must be provided for update"
});
export async function updateResource(
req: Request,
res: Response,
next: NextFunction,
next: NextFunction
): Promise<any> {
try {
const parsedParams = updateResourceParamsSchema.safeParse(req.params);
@ -40,8 +45,8 @@ export async function updateResource(
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedParams.error).toString(),
),
fromError(parsedParams.error).toString()
)
);
}
@ -50,8 +55,8 @@ export async function updateResource(
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedBody.error).toString(),
),
fromError(parsedBody.error).toString()
)
);
}
@ -68,8 +73,8 @@ export async function updateResource(
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Resource with ID ${resourceId} not found`,
),
`Resource with ID ${resourceId} not found`
)
);
}
@ -77,8 +82,8 @@ export async function updateResource(
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"Resource does not have a domain",
),
"Resource does not have a domain"
)
);
}
@ -88,7 +93,7 @@ export async function updateResource(
const updatePayload = {
...updateData,
...(fullDomain && { fullDomain }),
...(fullDomain && { fullDomain })
};
const updatedResource = await db
@ -101,8 +106,8 @@ export async function updateResource(
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Resource with ID ${resourceId} not found`,
),
`Resource with ID ${resourceId} not found`
)
);
}
@ -111,15 +116,12 @@ export async function updateResource(
success: true,
error: false,
message: "Resource updated successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"An error occurred",
),
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}

View file

@ -9,13 +9,17 @@ import logger from "@server/logger";
import { eq } from "drizzle-orm";
import { fromError } from "zod-validation-error";
const addRoleActionParamSchema = z.object({
roleId: z.string().transform(Number).pipe(z.number().int().positive()),
});
const addRoleActionParamSchema = z
.object({
roleId: z.string().transform(Number).pipe(z.number().int().positive())
})
.strict();
const addRoleActionSchema = z.object({
actionId: z.string(),
});
const addRoleActionSchema = z
.object({
actionId: z.string()
})
.strict();
export async function addRoleAction(
req: Request,
@ -66,7 +70,7 @@ export async function addRoleAction(
.values({
roleId,
actionId,
orgId: role[0].orgId!,
orgId: role[0].orgId!
})
.returning();
@ -75,7 +79,7 @@ export async function addRoleAction(
success: true,
error: false,
message: "Action added to role successfully",
status: HttpCode.CREATED,
status: HttpCode.CREATED
});
} catch (error) {
logger.error(error);

View file

@ -9,13 +9,17 @@ import logger from "@server/logger";
import { eq } from "drizzle-orm";
import { fromError } from "zod-validation-error";
const addRoleSiteParamsSchema = z.object({
roleId: z.string().transform(Number).pipe(z.number().int().positive()),
});
const addRoleSiteParamsSchema = z
.object({
roleId: z.string().transform(Number).pipe(z.number().int().positive())
})
.strict();
const addRoleSiteSchema = z.object({
siteId: z.string().transform(Number).pipe(z.number().int().positive()),
});
const addRoleSiteSchema = z
.object({
siteId: z.string().transform(Number).pipe(z.number().int().positive())
})
.strict();
export async function addRoleSite(
req: Request,
@ -51,7 +55,7 @@ export async function addRoleSite(
.insert(roleSites)
.values({
roleId,
siteId,
siteId
})
.returning();
@ -63,7 +67,7 @@ export async function addRoleSite(
for (const resource of siteResources) {
await db.insert(roleResources).values({
roleId,
resourceId: resource.resourceId,
resourceId: resource.resourceId
});
}
@ -72,7 +76,7 @@ export async function addRoleSite(
success: true,
error: false,
message: "Site added to role successfully",
status: HttpCode.CREATED,
status: HttpCode.CREATED
});
} catch (error) {
logger.error(error);

View file

@ -10,21 +10,23 @@ import { fromError } from "zod-validation-error";
import { ActionsEnum } from "@server/auth/actions";
import { eq, and } from "drizzle-orm";
const createRoleParamsSchema = z.object({
orgId: z.string(),
});
const createRoleParamsSchema = z
.object({
orgId: z.string()
})
.strict();
const createRoleSchema = z
.object({
name: z.string().min(1).max(255),
description: z.string().optional(),
description: z.string().optional()
})
.strict();
export const defaultRoleAllowedActions: ActionsEnum[] = [
ActionsEnum.getOrg,
ActionsEnum.getResource,
ActionsEnum.listResources,
ActionsEnum.listResources
];
export type CreateRoleBody = z.infer<typeof createRoleSchema>;
@ -64,7 +66,7 @@ export async function createRole(
const allRoles = await db
.select({
roleId: roles.roleId,
name: roles.name,
name: roles.name
})
.from(roles)
.leftJoin(orgs, eq(roles.orgId, orgs.orgId))
@ -84,7 +86,7 @@ export async function createRole(
.insert(roles)
.values({
...roleData,
orgId,
orgId
})
.returning();
@ -94,7 +96,7 @@ export async function createRole(
defaultRoleAllowedActions.map((action) => ({
roleId: newRole[0].roleId,
actionId: action,
orgId,
orgId
}))
)
.execute();
@ -104,7 +106,7 @@ export async function createRole(
success: true,
error: false,
message: "Role created successfully",
status: HttpCode.CREATED,
status: HttpCode.CREATED
});
} catch (error) {
logger.error(error);

View file

@ -9,13 +9,17 @@ import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
const deleteRoleSchema = z.object({
roleId: z.string().transform(Number).pipe(z.number().int().positive()),
});
const deleteRoleSchema = z
.object({
roleId: z.string().transform(Number).pipe(z.number().int().positive())
})
.strict();
const deelteRoleBodySchema = z.object({
roleId: z.string().transform(Number).pipe(z.number().int().positive()),
});
const deelteRoleBodySchema = z
.object({
roleId: z.string().transform(Number).pipe(z.number().int().positive())
})
.strict();
export async function deleteRole(
req: Request,
@ -108,7 +112,7 @@ export async function deleteRole(
success: true,
error: false,
message: "Role deleted successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);

View file

@ -9,9 +9,11 @@ import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
const getRoleSchema = z.object({
roleId: z.string().transform(Number).pipe(z.number().int().positive()),
});
const getRoleSchema = z
.object({
roleId: z.string().transform(Number).pipe(z.number().int().positive())
})
.strict();
export async function getRole(
req: Request,
@ -51,7 +53,7 @@ export async function getRole(
success: true,
error: false,
message: "Role retrieved successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);

View file

@ -9,9 +9,11 @@ import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
const listRoleActionsSchema = z.object({
roleId: z.string().transform(Number).pipe(z.number().int().positive()),
});
const listRoleActionsSchema = z
.object({
roleId: z.string().transform(Number).pipe(z.number().int().positive())
})
.strict();
export async function listRoleActions(
req: Request,
@ -35,7 +37,7 @@ export async function listRoleActions(
.select({
actionId: actions.actionId,
name: actions.name,
description: actions.description,
description: actions.description
})
.from(roleActions)
.innerJoin(actions, eq(roleActions.actionId, actions.actionId))
@ -48,7 +50,7 @@ export async function listRoleActions(
success: true,
error: false,
message: "Role actions retrieved successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);

View file

@ -9,9 +9,11 @@ import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
const listRoleResourcesSchema = z.object({
roleId: z.string().transform(Number).pipe(z.number().int().positive()),
});
const listRoleResourcesSchema = z
.object({
roleId: z.string().transform(Number).pipe(z.number().int().positive())
})
.strict();
export async function listRoleResources(
req: Request,
@ -35,7 +37,7 @@ export async function listRoleResources(
.select({
resourceId: resources.resourceId,
name: resources.name,
subdomain: resources.subdomain,
subdomain: resources.subdomain
})
.from(roleResources)
.innerJoin(
@ -51,7 +53,7 @@ export async function listRoleResources(
success: true,
error: false,
message: "Role resources retrieved successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);

View file

@ -9,9 +9,11 @@ import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
const listRoleSitesSchema = z.object({
roleId: z.string().transform(Number).pipe(z.number().int().positive()),
});
const listRoleSitesSchema = z
.object({
roleId: z.string().transform(Number).pipe(z.number().int().positive())
})
.strict();
export async function listRoleSites(
req: Request,
@ -34,7 +36,7 @@ export async function listRoleSites(
const roleSitesList = await db
.select({
siteId: sites.siteId,
name: sites.name,
name: sites.name
})
.from(roleSites)
.innerJoin(sites, eq(roleSites.siteId, sites.siteId))
@ -47,7 +49,7 @@ export async function listRoleSites(
success: true,
error: false,
message: "Role sites retrieved successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);

View file

@ -10,9 +10,11 @@ import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import stoi from "@server/utils/stoi";
const listRolesParamsSchema = z.object({
orgId: z.string(),
});
const listRolesParamsSchema = z
.object({
orgId: z.string()
})
.strict();
const listRolesSchema = z.object({
limit: z
@ -26,7 +28,7 @@ const listRolesSchema = z.object({
.optional()
.default("0")
.transform(Number)
.pipe(z.number().int().nonnegative()),
.pipe(z.number().int().nonnegative())
});
async function queryRoles(orgId: string, limit: number, offset: number) {
@ -37,7 +39,7 @@ async function queryRoles(orgId: string, limit: number, offset: number) {
isAdmin: roles.isAdmin,
name: roles.name,
description: roles.description,
orgName: orgs.name,
orgName: orgs.name
})
.from(roles)
.leftJoin(orgs, eq(roles.orgId, orgs.orgId))
@ -100,13 +102,13 @@ export async function listRoles(
pagination: {
total: totalCount,
limit,
offset,
},
offset
}
},
success: true,
error: false,
message: "Roles retrieved successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);

View file

@ -9,13 +9,17 @@ import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
const removeRoleActionParamsSchema = z.object({
roleId: z.string().transform(Number).pipe(z.number().int().positive()),
});
const removeRoleActionParamsSchema = z
.object({
roleId: z.string().transform(Number).pipe(z.number().int().positive())
})
.strict();
const removeRoleActionSchema = z.object({
actionId: z.string(),
});
const removeRoleActionSchema = z
.object({
actionId: z.string()
})
.strict();
export async function removeRoleAction(
req: Request,
@ -71,7 +75,7 @@ export async function removeRoleAction(
success: true,
error: false,
message: "Action removed from role successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);

View file

@ -9,13 +9,20 @@ import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
const removeRoleResourceParamsSchema = z.object({
roleId: z.string().transform(Number).pipe(z.number().int().positive()),
});
const removeRoleResourceParamsSchema = z
.object({
roleId: z.string().transform(Number).pipe(z.number().int().positive())
})
.strict();
const removeRoleResourceSchema = z.object({
resourceId: z.string().transform(Number).pipe(z.number().int().positive()),
});
const removeRoleResourceSchema = z
.object({
resourceId: z
.string()
.transform(Number)
.pipe(z.number().int().positive())
})
.strict();
export async function removeRoleResource(
req: Request,
@ -71,7 +78,7 @@ export async function removeRoleResource(
success: true,
error: false,
message: "Resource removed from role successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);

View file

@ -9,13 +9,17 @@ import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
const removeRoleSiteParamsSchema = z.object({
roleId: z.string().transform(Number).pipe(z.number().int().positive()),
});
const removeRoleSiteParamsSchema = z
.object({
roleId: z.string().transform(Number).pipe(z.number().int().positive())
})
.strict();
const removeRoleSiteSchema = z.object({
siteId: z.string().transform(Number).pipe(z.number().int().positive()),
});
const removeRoleSiteSchema = z
.object({
siteId: z.string().transform(Number).pipe(z.number().int().positive())
})
.strict();
export async function removeRoleSite(
req: Request,
@ -85,7 +89,7 @@ export async function removeRoleSite(
success: true,
error: false,
message: "Site removed from role successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);

View file

@ -9,18 +9,20 @@ import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
const updateRoleParamsSchema = z.object({
roleId: z.string().transform(Number).pipe(z.number().int().positive()),
});
const updateRoleParamsSchema = z
.object({
roleId: z.string().transform(Number).pipe(z.number().int().positive())
})
.strict();
const updateRoleBodySchema = z
.object({
name: z.string().min(1).max(255).optional(),
description: z.string().optional(),
description: z.string().optional()
})
.strict()
.refine((data) => Object.keys(data).length > 0, {
message: "At least one field must be provided for update",
message: "At least one field must be provided for update"
});
export async function updateRole(
@ -96,7 +98,7 @@ export async function updateRole(
success: true,
error: false,
message: "Role updated successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);

View file

@ -1,7 +1,7 @@
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { db } from "@server/db";
import { roles, userSites, sites, roleSites } from "@server/db/schema";
import { roles, userSites, sites, roleSites, Site } from "@server/db/schema";
import response from "@server/utils/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
@ -14,9 +14,11 @@ import { hash } from "@node-rs/argon2";
import { newts } from "@server/db/schema";
import moment from "moment";
const createSiteParamsSchema = z.object({
orgId: z.string(),
});
const createSiteParamsSchema = z
.object({
orgId: z.string()
})
.strict();
const createSiteSchema = z
.object({
@ -27,18 +29,13 @@ const createSiteSchema = z
subnet: z.string(),
newtId: z.string().optional(),
secret: z.string().optional(),
type: z.string(),
type: z.string()
})
.strict();
export type CreateSiteBody = z.infer<typeof createSiteSchema>;
export type CreateSiteResponse = {
name: string;
siteId: number;
orgId: string;
niceId: string;
};
export type CreateSiteResponse = Site;
export async function createSite(
req: Request,
@ -85,14 +82,14 @@ export async function createSite(
name,
niceId,
subnet,
type,
type
};
if (pubKey && type == "wireguard") {
// we dont add the pubKey for newts because the newt will generate it
payload = {
...payload,
pubKey,
pubKey
};
}
@ -112,14 +109,14 @@ export async function createSite(
await db.insert(roleSites).values({
roleId: adminRole[0].roleId,
siteId: newSite.siteId,
siteId: newSite.siteId
});
if (req.userOrgRoleId != adminRole[0].roleId) {
// make sure the user can access the site
db.insert(userSites).values({
userId: req.user?.userId!,
siteId: newSite.siteId,
siteId: newSite.siteId
});
}
@ -129,14 +126,14 @@ export async function createSite(
memoryCost: 19456,
timeCost: 2,
outputLen: 32,
parallelism: 1,
parallelism: 1
});
await db.insert(newts).values({
newtId: newtId!,
secretHash,
siteId: newSite.siteId,
dateCreated: moment().toISOString(),
dateCreated: moment().toISOString()
});
} else if (type == "wireguard") {
if (!pubKey) {
@ -149,23 +146,19 @@ export async function createSite(
}
await addPeer(exitNodeId, {
publicKey: pubKey,
allowedIps: [],
allowedIps: []
});
}
return response(res, {
data: {
name: newSite.name,
niceId: newSite.niceId,
siteId: newSite.siteId,
orgId: newSite.orgId,
},
return response<CreateSiteResponse>(res, {
data: newSite,
success: true,
error: false,
message: "Site created successfully",
status: HttpCode.CREATED,
status: HttpCode.CREATED
});
} catch (error) {
logger.error(error);
return next(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);

View file

@ -11,9 +11,11 @@ import { deletePeer } from "../gerbil/peers";
import { fromError } from "zod-validation-error";
import { sendToClient } from "../ws";
const deleteSiteSchema = z.object({
siteId: z.string().transform(Number).pipe(z.number().int().positive()),
});
const deleteSiteSchema = z
.object({
siteId: z.string().transform(Number).pipe(z.number().int().positive())
})
.strict();
export async function deleteSite(
req: Request,
@ -60,7 +62,7 @@ export async function deleteSite(
if (deletedNewt) {
const payload = {
type: `newt/terminate`,
data: {},
data: {}
};
sendToClient(deletedNewt.newtId, payload);
@ -79,7 +81,7 @@ export async function deleteSite(
success: true,
error: false,
message: "Site deleted successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);

View file

@ -10,16 +10,18 @@ import logger from "@server/logger";
import stoi from "@server/utils/stoi";
import { fromError } from "zod-validation-error";
const getSiteSchema = z.object({
siteId: z
.string()
.optional()
.transform(stoi)
.pipe(z.number().int().positive().optional())
.optional(),
niceId: z.string().optional(),
orgId: z.string().optional(),
});
const getSiteSchema = z
.object({
siteId: z
.string()
.optional()
.transform(stoi)
.pipe(z.number().int().positive().optional())
.optional(),
niceId: z.string().optional(),
orgId: z.string().optional()
})
.strict();
export type GetSiteResponse = {
siteId: number;
@ -79,15 +81,15 @@ export async function getSite(
siteId: site[0].siteId,
niceId: site[0].niceId,
name: site[0].name,
subnet: site[0].subnet,
subnet: site[0].subnet
},
success: true,
error: false,
message: "Site retrieved successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error("Error from getSite: ", error);
logger.error(error);
return next(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);

View file

@ -9,9 +9,11 @@ import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
const listSiteRolesSchema = z.object({
siteId: z.string().transform(Number).pipe(z.number().int().positive()),
});
const listSiteRolesSchema = z
.object({
siteId: z.string().transform(Number).pipe(z.number().int().positive())
})
.strict();
export async function listSiteRoles(
req: Request,
@ -36,7 +38,7 @@ export async function listSiteRoles(
roleId: roles.roleId,
name: roles.name,
description: roles.description,
isAdmin: roles.isAdmin,
isAdmin: roles.isAdmin
})
.from(roleSites)
.innerJoin(roles, eq(roleSites.roleId, roles.roleId))
@ -47,7 +49,7 @@ export async function listSiteRoles(
success: true,
error: false,
message: "Site roles retrieved successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);

View file

@ -1,5 +1,6 @@
import { db } from "@server/db";
import { orgs, roleSites, sites, userSites } from "@server/db/schema";
import logger from "@server/logger";
import HttpCode from "@server/types/HttpCode";
import response from "@server/utils/response";
import { and, count, eq, inArray, or, sql } from "drizzle-orm";
@ -8,9 +9,11 @@ import createHttpError from "http-errors";
import { z } from "zod";
import { fromError } from "zod-validation-error";
const listSitesParamsSchema = z.object({
orgId: z.string(),
});
const listSitesParamsSchema = z
.object({
orgId: z.string()
})
.strict();
const listSitesSchema = z.object({
limit: z
@ -24,7 +27,7 @@ const listSitesSchema = z.object({
.optional()
.default("0")
.transform(Number)
.pipe(z.number().int().nonnegative()),
.pipe(z.number().int().nonnegative())
});
function querySites(orgId: string, accessibleSiteIds: number[]) {
@ -39,15 +42,15 @@ function querySites(orgId: string, accessibleSiteIds: number[]) {
megabytesOut: sites.megabytesOut,
orgName: orgs.name,
type: sites.type,
online: sites.online,
online: sites.online
})
.from(sites)
.leftJoin(orgs, eq(sites.orgId, orgs.orgId))
.where(
and(
inArray(sites.siteId, accessibleSiteIds),
eq(sites.orgId, orgId),
),
eq(sites.orgId, orgId)
)
);
}
@ -59,7 +62,7 @@ export type ListSitesResponse = {
export async function listSites(
req: Request,
res: Response,
next: NextFunction,
next: NextFunction
): Promise<any> {
try {
const parsedQuery = listSitesSchema.safeParse(req.query);
@ -67,8 +70,8 @@ export async function listSites(
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedQuery.error),
),
fromError(parsedQuery.error)
)
);
}
const { limit, offset } = parsedQuery.data;
@ -78,8 +81,8 @@ export async function listSites(
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedParams.error),
),
fromError(parsedParams.error)
)
);
}
const { orgId } = parsedParams.data;
@ -88,22 +91,22 @@ export async function listSites(
return next(
createHttpError(
HttpCode.FORBIDDEN,
"User does not have access to this organization",
),
"User does not have access to this organization"
)
);
}
const accessibleSites = await db
.select({
siteId: sql<number>`COALESCE(${userSites.siteId}, ${roleSites.siteId})`,
siteId: sql<number>`COALESCE(${userSites.siteId}, ${roleSites.siteId})`
})
.from(userSites)
.fullJoin(roleSites, eq(userSites.siteId, roleSites.siteId))
.where(
or(
eq(userSites.userId, req.user!.userId),
eq(roleSites.roleId, req.userOrgRoleId!),
),
eq(roleSites.roleId, req.userOrgRoleId!)
)
);
const accessibleSiteIds = accessibleSites.map((site) => site.siteId);
@ -115,8 +118,8 @@ export async function listSites(
.where(
and(
inArray(sites.siteId, accessibleSiteIds),
eq(sites.orgId, orgId),
),
eq(sites.orgId, orgId)
)
);
const sitesList = await baseQuery.limit(limit).offset(offset);
@ -129,20 +132,18 @@ export async function listSites(
pagination: {
total: totalCount,
limit,
offset,
},
offset
}
},
success: true,
error: false,
message: "Sites retrieved successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"An error occurred",
),
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}

View file

@ -9,14 +9,16 @@ import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
const updateSiteParamsSchema = z.object({
siteId: z.string().transform(Number).pipe(z.number().int().positive()),
});
const updateSiteParamsSchema = z
.object({
siteId: z.string().transform(Number).pipe(z.number().int().positive())
})
.strict();
const updateSiteBodySchema = z
.object({
name: z.string().min(1).max(255).optional(),
subdomain: z.string().min(1).max(255).optional(),
subdomain: z.string().min(1).max(255).optional()
// pubKey: z.string().optional(),
// subnet: z.string().optional(),
// exitNode: z.number().int().positive().optional(),
@ -25,7 +27,7 @@ const updateSiteBodySchema = z
})
.strict()
.refine((data) => Object.keys(data).length > 0, {
message: "At least one field must be provided for update",
message: "At least one field must be provided for update"
});
export async function updateSite(
@ -77,7 +79,7 @@ export async function updateSite(
success: true,
error: false,
message: "Site updated successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);

View file

@ -12,9 +12,14 @@ import { isIpInCidr } from "@server/utils/ip";
import { fromError } from "zod-validation-error";
import { addTargets } from "../newt/targets";
const createTargetParamsSchema = z.object({
resourceId: z.string().transform(Number).pipe(z.number().int().positive()),
});
const createTargetParamsSchema = z
.object({
resourceId: z
.string()
.transform(Number)
.pipe(z.number().int().positive())
})
.strict();
const createTargetSchema = z
.object({
@ -22,7 +27,7 @@ const createTargetSchema = z
method: z.string().min(1).max(10),
port: z.number().int().min(1).max(65535),
protocol: z.string().optional(),
enabled: z.boolean().default(true),
enabled: z.boolean().default(true)
})
.strict();
@ -61,7 +66,7 @@ export async function createTarget(
// get the resource
const [resource] = await db
.select({
siteId: resources.siteId,
siteId: resources.siteId
})
.from(resources)
.where(eq(resources.resourceId, resourceId));
@ -91,7 +96,10 @@ export async function createTarget(
}
// make sure the target is within the site subnet
if (site.type == "wireguard" && !isIpInCidr(targetData.ip, site.subnet!)) {
if (
site.type == "wireguard" &&
!isIpInCidr(targetData.ip, site.subnet!)
) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
@ -102,7 +110,7 @@ export async function createTarget(
// Fetch resources for this site
const resourcesRes = await db.query.resources.findMany({
where: eq(resources.siteId, site.siteId),
where: eq(resources.siteId, site.siteId)
});
// TODO: is this all inefficient?
@ -112,7 +120,7 @@ export async function createTarget(
await Promise.all(
resourcesRes.map(async (resource) => {
const targetsRes = await db.query.targets.findMany({
where: eq(targets.resourceId, resource.resourceId),
where: eq(targets.resourceId, resource.resourceId)
});
targetsRes.forEach((target) => {
targetIps.push(`${target.ip}/32`);
@ -147,7 +155,7 @@ export async function createTarget(
resourceId,
protocol: "tcp", // hard code for now
internalPort,
...targetData,
...targetData
})
.returning();
@ -155,7 +163,7 @@ export async function createTarget(
if (site.type == "wireguard") {
await addPeer(site.exitNodeId!, {
publicKey: site.pubKey,
allowedIps: targetIps.flat(),
allowedIps: targetIps.flat()
});
} else if (site.type == "newt") {
// get the newt on the site by querying the newt table for siteId
@ -174,7 +182,7 @@ export async function createTarget(
success: true,
error: false,
message: "Target created successfully",
status: HttpCode.CREATED,
status: HttpCode.CREATED
});
} catch (error) {
logger.error(error);

View file

@ -11,9 +11,11 @@ import { addPeer } from "../gerbil/peers";
import { fromError } from "zod-validation-error";
import { removeTargets } from "../newt/targets";
const deleteTargetSchema = z.object({
targetId: z.string().transform(Number).pipe(z.number().int().positive()),
});
const deleteTargetSchema = z
.object({
targetId: z.string().transform(Number).pipe(z.number().int().positive())
})
.strict();
export async function deleteTarget(
req: Request,
@ -49,7 +51,7 @@ export async function deleteTarget(
// get the resource
const [resource] = await db
.select({
siteId: resources.siteId,
siteId: resources.siteId
})
.from(resources)
.where(eq(resources.resourceId, deletedTarget.resourceId!));
@ -83,14 +85,14 @@ export async function deleteTarget(
// TODO: is this all inefficient?
// Fetch resources for this site
const resourcesRes = await db.query.resources.findMany({
where: eq(resources.siteId, site.siteId),
where: eq(resources.siteId, site.siteId)
});
// Fetch targets for all resources of this site
const targetIps = await Promise.all(
resourcesRes.map(async (resource) => {
const targetsRes = await db.query.targets.findMany({
where: eq(targets.resourceId, resource.resourceId),
where: eq(targets.resourceId, resource.resourceId)
});
return targetsRes.map((target) => `${target.ip}/32`);
})
@ -98,7 +100,7 @@ export async function deleteTarget(
await addPeer(site.exitNodeId!, {
publicKey: site.pubKey,
allowedIps: targetIps.flat(),
allowedIps: targetIps.flat()
});
} else if (site.type == "newt") {
// get the newt on the site by querying the newt table for siteId
@ -117,7 +119,7 @@ export async function deleteTarget(
success: true,
error: false,
message: "Target deleted successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);

View file

@ -9,9 +9,11 @@ import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
const getTargetSchema = z.object({
targetId: z.string().transform(Number).pipe(z.number().int().positive()),
});
const getTargetSchema = z
.object({
targetId: z.string().transform(Number).pipe(z.number().int().positive())
})
.strict();
export async function getTarget(
req: Request,
@ -51,7 +53,7 @@ export async function getTarget(
success: true,
error: false,
message: "Target retrieved successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);

View file

@ -9,9 +9,14 @@ import { z } from "zod";
import { fromError } from "zod-validation-error";
import logger from "@server/logger";
const listTargetsParamsSchema = z.object({
resourceId: z.string().transform(Number).pipe(z.number().int().positive()),
});
const listTargetsParamsSchema = z
.object({
resourceId: z
.string()
.transform(Number)
.pipe(z.number().int().positive())
})
.strict();
const listTargetsSchema = z.object({
limit: z
@ -25,7 +30,7 @@ const listTargetsSchema = z.object({
.optional()
.default("0")
.transform(Number)
.pipe(z.number().int().nonnegative()),
.pipe(z.number().int().nonnegative())
});
function queryTargets(resourceId: number) {
@ -37,7 +42,7 @@ function queryTargets(resourceId: number) {
port: targets.port,
protocol: targets.protocol,
enabled: targets.enabled,
resourceId: targets.resourceId,
resourceId: targets.resourceId
// resourceName: resources.name,
})
.from(targets)
@ -97,13 +102,13 @@ export async function listTargets(
pagination: {
total: totalCount,
limit,
offset,
},
offset
}
},
success: true,
error: false,
message: "Targets retrieved successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);

View file

@ -11,20 +11,22 @@ import { fromError } from "zod-validation-error";
import { addPeer } from "../gerbil/peers";
import { addTargets } from "../newt/targets";
const updateTargetParamsSchema = z.object({
targetId: z.string().transform(Number).pipe(z.number().int().positive()),
});
const updateTargetParamsSchema = z
.object({
targetId: z.string().transform(Number).pipe(z.number().int().positive())
})
.strict();
const updateTargetBodySchema = z
.object({
ip: z.string().ip().optional(), // for now we cant update the ip; you will have to delete
method: z.string().min(1).max(10).optional(),
port: z.number().int().min(1).max(65535).optional(),
enabled: z.boolean().optional(),
enabled: z.boolean().optional()
})
.strict()
.refine((data) => Object.keys(data).length > 0, {
message: "At least one field must be provided for update",
message: "At least one field must be provided for update"
});
export async function updateTarget(
@ -74,7 +76,7 @@ export async function updateTarget(
// get the resource
const [resource] = await db
.select({
siteId: resources.siteId,
siteId: resources.siteId
})
.from(resources)
.where(eq(resources.resourceId, updatedTarget.resourceId!));
@ -107,14 +109,14 @@ export async function updateTarget(
// TODO: is this all inefficient?
// Fetch resources for this site
const resourcesRes = await db.query.resources.findMany({
where: eq(resources.siteId, site.siteId),
where: eq(resources.siteId, site.siteId)
});
// Fetch targets for all resources of this site
const targetIps = await Promise.all(
resourcesRes.map(async (resource) => {
const targetsRes = await db.query.targets.findMany({
where: eq(targets.resourceId, resource.resourceId),
where: eq(targets.resourceId, resource.resourceId)
});
return targetsRes.map((target) => `${target.ip}/32`);
})
@ -122,7 +124,7 @@ export async function updateTarget(
await addPeer(site.exitNodeId!, {
publicKey: site.pubKey,
allowedIps: targetIps.flat(),
allowedIps: targetIps.flat()
});
} else if (site.type == "newt") {
// get the newt on the site by querying the newt table for siteId
@ -140,7 +142,7 @@ export async function updateTarget(
success: true,
error: false,
message: "Target updated successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);

View file

@ -11,10 +11,12 @@ import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { isWithinExpirationDate } from "oslo";
const acceptInviteBodySchema = z.object({
token: z.string(),
inviteId: z.string(),
});
const acceptInviteBodySchema = z
.object({
token: z.string(),
inviteId: z.string()
})
.strict();
export type AcceptInviteResponse = {
accepted: boolean;
@ -64,7 +66,7 @@ export async function acceptInvite(
memoryCost: 19456,
timeCost: 2,
outputLen: 32,
parallelism: 1,
parallelism: 1
});
if (!validToken) {
return next(
@ -121,7 +123,7 @@ export async function acceptInvite(
await db.insert(userOrgs).values({
userId: existingUser[0].userId,
orgId: existingInvite[0].orgId,
roleId: existingInvite[0].roleId,
roleId: existingInvite[0].roleId
});
// delete the invite
@ -132,7 +134,7 @@ export async function acceptInvite(
success: true,
error: false,
message: "Invite accepted",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);

View file

@ -9,11 +9,13 @@ import logger from "@server/logger";
import { eq } from "drizzle-orm";
import { fromError } from "zod-validation-error";
const addUserActionSchema = z.object({
userId: z.string(),
actionId: z.string(),
orgId: z.string(),
});
const addUserActionSchema = z
.object({
userId: z.string(),
actionId: z.string(),
orgId: z.string()
})
.strict();
export async function addUserAction(
req: Request,
@ -52,7 +54,7 @@ export async function addUserAction(
.values({
userId,
actionId,
orgId,
orgId
})
.returning();
@ -61,7 +63,7 @@ export async function addUserAction(
success: true,
error: false,
message: "Action added to user successfully",
status: HttpCode.CREATED,
status: HttpCode.CREATED
});
} catch (error) {
logger.error(error);

View file

@ -10,10 +10,12 @@ import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import stoi from "@server/utils/stoi";
const addUserRoleParamsSchema = z.object({
userId: z.string(),
roleId: z.string().transform(stoi).pipe(z.number()),
});
const addUserRoleParamsSchema = z
.object({
userId: z.string(),
roleId: z.string().transform(stoi).pipe(z.number())
})
.strict();
export type AddUserRoleResponse = z.infer<typeof addUserRoleParamsSchema>;
@ -96,7 +98,7 @@ export async function addUserRole(
success: true,
error: false,
message: "Role added to user successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);

View file

@ -9,10 +9,12 @@ import logger from "@server/logger";
import { eq } from "drizzle-orm";
import { fromError } from "zod-validation-error";
const addUserSiteSchema = z.object({
userId: z.string(),
siteId: z.string().transform(Number).pipe(z.number().int().positive()),
});
const addUserSiteSchema = z
.object({
userId: z.string(),
siteId: z.string().transform(Number).pipe(z.number().int().positive())
})
.strict();
export async function addUserSite(
req: Request,
@ -36,7 +38,7 @@ export async function addUserSite(
.insert(userSites)
.values({
userId,
siteId,
siteId
})
.returning();
@ -48,7 +50,7 @@ export async function addUserSite(
for (const resource of siteResources) {
await db.insert(userResources).values({
userId,
resourceId: resource.resourceId,
resourceId: resource.resourceId
});
}
@ -57,7 +59,7 @@ export async function addUserSite(
success: true,
error: false,
message: "Site added to user successfully",
status: HttpCode.CREATED,
status: HttpCode.CREATED
});
} catch (error) {
logger.error(error);

View file

@ -19,7 +19,7 @@ async function queryUser(orgId: string, userId: string) {
roleId: userOrgs.roleId,
roleName: roles.name,
isOwner: userOrgs.isOwner,
isAdmin: roles.isAdmin,
isAdmin: roles.isAdmin
})
.from(userOrgs)
.leftJoin(roles, eq(userOrgs.roleId, roles.roleId))
@ -33,10 +33,12 @@ export type GetOrgUserResponse = NonNullable<
Awaited<ReturnType<typeof queryUser>>
>;
const getOrgUserParamsSchema = z.object({
userId: z.string(),
orgId: z.string(),
});
const getOrgUserParamsSchema = z
.object({
userId: z.string(),
orgId: z.string()
})
.strict();
export async function getOrgUser(
req: Request,
@ -109,7 +111,7 @@ export async function getOrgUser(
success: true,
error: false,
message: "User retrieved successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);

View file

@ -6,7 +6,6 @@ import { and, eq } from "drizzle-orm";
import response from "@server/utils/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions";
import logger from "@server/logger";
import { alphabet, generateRandomString } from "oslo/crypto";
import { createDate, TimeSpan } from "oslo";
@ -16,15 +15,20 @@ import { fromError } from "zod-validation-error";
import { sendEmail } from "@server/emails";
import SendInviteLink from "@server/emails/templates/SendInviteLink";
const inviteUserParamsSchema = z.object({
orgId: z.string(),
});
const inviteUserParamsSchema = z
.object({
orgId: z.string()
})
.strict();
const inviteUserBodySchema = z.object({
email: z.string().email(),
roleId: z.number(),
validHours: z.number().gt(0).lte(168),
});
const inviteUserBodySchema = z
.object({
email: z.string().email(),
roleId: z.number(),
validHours: z.number().gt(0).lte(168),
sendEmail: z.boolean().optional()
})
.strict();
export type InviteUserBody = z.infer<typeof inviteUserBodySchema>;
@ -38,7 +42,7 @@ const inviteTracker: Record<string, { timestamps: number[] }> = {};
export async function inviteUser(
req: Request,
res: Response,
next: NextFunction,
next: NextFunction
): Promise<any> {
try {
const parsedParams = inviteUserParamsSchema.safeParse(req.params);
@ -46,8 +50,8 @@ export async function inviteUser(
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedParams.error).toString(),
),
fromError(parsedParams.error).toString()
)
);
}
@ -56,13 +60,18 @@ export async function inviteUser(
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedBody.error).toString(),
),
fromError(parsedBody.error).toString()
)
);
}
const { orgId } = parsedParams.data;
const { email, validHours, roleId } = parsedBody.data;
const {
email,
validHours,
roleId,
sendEmail: doEmail
} = parsedBody.data;
const currentTime = Date.now();
const oneHourAgo = currentTime - 3600000;
@ -79,8 +88,8 @@ export async function inviteUser(
return next(
createHttpError(
HttpCode.TOO_MANY_REQUESTS,
"User has already been invited 3 times in the last hour",
),
"User has already been invited 3 times in the last hour"
)
);
}
@ -93,7 +102,7 @@ export async function inviteUser(
.limit(1);
if (!org.length) {
return next(
createHttpError(HttpCode.NOT_FOUND, "Organization not found"),
createHttpError(HttpCode.NOT_FOUND, "Organization not found")
);
}
@ -107,14 +116,14 @@ export async function inviteUser(
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"User is already a member of this organization",
),
"User is already a member of this organization"
)
);
}
const inviteId = generateRandomString(
10,
alphabet("a-z", "A-Z", "0-9"),
alphabet("a-z", "A-Z", "0-9")
);
const token = generateRandomString(32, alphabet("a-z", "A-Z", "0-9"));
const expiresAt = createDate(new TimeSpan(validHours, "h")).getTime();
@ -125,7 +134,7 @@ export async function inviteUser(
await db
.delete(userInvites)
.where(
and(eq(userInvites.email, email), eq(userInvites.orgId, orgId)),
and(eq(userInvites.email, email), eq(userInvites.orgId, orgId))
)
.execute();
@ -135,43 +144,42 @@ export async function inviteUser(
email,
expiresAt,
tokenHash,
roleId,
roleId
});
const inviteLink = `${config.app.base_url}/invite?token=${inviteId}-${token}`;
await sendEmail(
SendInviteLink({
email,
inviteLink,
expiresInDays: (validHours / 24).toString(),
orgName: org[0].name || orgId,
inviterName: req.user?.email,
}),
{
to: email,
from: config.email?.no_reply,
subject: "You're invited to join a Fossorial organization",
},
);
if (doEmail) {
await sendEmail(
SendInviteLink({
email,
inviteLink,
expiresInDays: (validHours / 24).toString(),
orgName: org[0].name || orgId,
inviterName: req.user?.email
}),
{
to: email,
from: config.email?.no_reply,
subject: "You're invited to join a Fossorial organization"
}
);
}
return response<InviteUserResponse>(res, {
data: {
inviteLink,
expiresAt,
expiresAt
},
success: true,
error: false,
message: "User invited successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
console.error(error);
logger.error(error);
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"An error occurred",
),
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}

View file

@ -8,24 +8,28 @@ import createHttpError from "http-errors";
import { sql } from "drizzle-orm";
import logger from "@server/logger";
const listUsersParamsSchema = z.object({
orgId: z.string(),
});
const listUsersParamsSchema = z
.object({
orgId: z.string()
})
.strict();
const listUsersSchema = z.object({
limit: z
.string()
.optional()
.default("1000")
.transform(Number)
.pipe(z.number().int().nonnegative()),
offset: z
.string()
.optional()
.default("0")
.transform(Number)
.pipe(z.number().int().nonnegative()),
});
const listUsersSchema = z
.object({
limit: z
.string()
.optional()
.default("1000")
.transform(Number)
.pipe(z.number().int().nonnegative()),
offset: z
.string()
.optional()
.default("0")
.transform(Number)
.pipe(z.number().int().nonnegative())
})
.strict();
async function queryUsers(orgId: string, limit: number, offset: number) {
return await db
@ -37,7 +41,7 @@ async function queryUsers(orgId: string, limit: number, offset: number) {
orgId: userOrgs.orgId,
roleId: userOrgs.roleId,
roleName: roles.name,
isOwner: userOrgs.isOwner,
isOwner: userOrgs.isOwner
})
.from(users)
.leftJoin(userOrgs, sql`${users.userId} = ${userOrgs.userId}`)
@ -97,13 +101,13 @@ export async function listUsers(
pagination: {
total: count,
limit,
offset,
},
offset
}
},
success: true,
error: false,
message: "Users retrieved successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);

View file

@ -9,14 +9,18 @@ import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
const removeUserActionParamsSchema = z.object({
userId: z.string(),
});
const removeUserActionParamsSchema = z
.object({
userId: z.string()
})
.strict();
const removeUserActionSchema = z.object({
actionId: z.string(),
orgId: z.string(),
});
const removeUserActionSchema = z
.object({
actionId: z.string(),
orgId: z.string()
})
.strict();
export async function removeUserAction(
req: Request,
@ -73,7 +77,7 @@ export async function removeUserAction(
success: true,
error: false,
message: "Action removed from user successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);

View file

@ -9,10 +9,12 @@ import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
const removeUserSchema = z.object({
userId: z.string(),
orgId: z.string(),
});
const removeUserSchema = z
.object({
userId: z.string(),
orgId: z.string()
})
.strict();
export async function removeUserOrg(
req: Request,
@ -70,7 +72,7 @@ export async function removeUserOrg(
success: true,
error: false,
message: "User remove from org successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);

View file

@ -9,10 +9,15 @@ import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
const removeUserResourceSchema = z.object({
userId: z.string(),
resourceId: z.string().transform(Number).pipe(z.number().int().positive()),
});
const removeUserResourceSchema = z
.object({
userId: z.string(),
resourceId: z
.string()
.transform(Number)
.pipe(z.number().int().positive())
})
.strict();
export async function removeUserResource(
req: Request,
@ -56,7 +61,7 @@ export async function removeUserResource(
success: true,
error: false,
message: "Resource removed from user successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);

View file

@ -9,13 +9,17 @@ import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
const removeUserSiteParamsSchema = z.object({
userId: z.string(),
});
const removeUserSiteParamsSchema = z
.object({
userId: z.string()
})
.strict();
const removeUserSiteSchema = z.object({
siteId: z.number().int().positive(),
});
const removeUserSiteSchema = z
.object({
siteId: z.number().int().positive()
})
.strict();
export async function removeUserSite(
req: Request,
@ -85,7 +89,7 @@ export async function removeUserSite(
success: true,
error: false,
message: "Site removed from user successfully",
status: HttpCode.OK,
status: HttpCode.OK
});
} catch (error) {
logger.error(error);