fix issues from test deploy
This commit is contained in:
parent
3fb3be1f1e
commit
ce5df3b0b9
92 changed files with 1410 additions and 1019 deletions
|
@ -25,7 +25,8 @@ gerbil:
|
||||||
block_size: 16
|
block_size: 16
|
||||||
subnet_group: 10.0.0.0/8
|
subnet_group: 10.0.0.0/8
|
||||||
|
|
||||||
rate_limit:
|
rate_limits:
|
||||||
|
global:
|
||||||
window_minutes: 1
|
window_minutes: 1
|
||||||
max_requests: 100
|
max_requests: 100
|
||||||
|
|
||||||
|
|
|
@ -38,9 +38,9 @@ export function createApiServer() {
|
||||||
if (!dev) {
|
if (!dev) {
|
||||||
apiServer.use(
|
apiServer.use(
|
||||||
rateLimitMiddleware({
|
rateLimitMiddleware({
|
||||||
windowMin: config.rate_limit.window_minutes,
|
windowMin: config.rate_limits.global.window_minutes,
|
||||||
max: config.rate_limit.max_requests,
|
max: config.rate_limits.global.max_requests,
|
||||||
type: "IP_ONLY",
|
type: "IP_AND_PATH",
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ const environmentSchema = z.object({
|
||||||
app: z.object({
|
app: z.object({
|
||||||
base_url: z.string().url(),
|
base_url: z.string().url(),
|
||||||
log_level: z.enum(["debug", "info", "warn", "error"]),
|
log_level: z.enum(["debug", "info", "warn", "error"]),
|
||||||
save_logs: z.boolean(),
|
save_logs: z.boolean()
|
||||||
}),
|
}),
|
||||||
server: z.object({
|
server: z.object({
|
||||||
external_port: portSchema,
|
external_port: portSchema,
|
||||||
|
@ -26,24 +26,32 @@ const environmentSchema = z.object({
|
||||||
secure_cookies: z.boolean(),
|
secure_cookies: z.boolean(),
|
||||||
signup_secret: z.string().optional(),
|
signup_secret: z.string().optional(),
|
||||||
session_cookie_name: z.string(),
|
session_cookie_name: z.string(),
|
||||||
resource_session_cookie_name: z.string(),
|
resource_session_cookie_name: z.string()
|
||||||
}),
|
}),
|
||||||
traefik: z.object({
|
traefik: z.object({
|
||||||
http_entrypoint: z.string(),
|
http_entrypoint: z.string(),
|
||||||
https_entrypoint: z.string().optional(),
|
https_entrypoint: z.string().optional(),
|
||||||
cert_resolver: z.string().optional(),
|
cert_resolver: z.string().optional(),
|
||||||
prefer_wildcard_cert: z.boolean().optional(),
|
prefer_wildcard_cert: z.boolean().optional()
|
||||||
}),
|
}),
|
||||||
gerbil: z.object({
|
gerbil: z.object({
|
||||||
start_port: portSchema,
|
start_port: portSchema,
|
||||||
base_endpoint: z.string(),
|
base_endpoint: z.string(),
|
||||||
use_subdomain: z.boolean(),
|
use_subdomain: z.boolean(),
|
||||||
subnet_group: z.string(),
|
subnet_group: z.string(),
|
||||||
block_size: z.number().positive().gt(0),
|
block_size: z.number().positive().gt(0)
|
||||||
}),
|
}),
|
||||||
rate_limit: z.object({
|
rate_limits: z.object({
|
||||||
|
global: z.object({
|
||||||
window_minutes: z.number().positive().gt(0),
|
window_minutes: z.number().positive().gt(0),
|
||||||
max_requests: z.number().positive().gt(0),
|
max_requests: z.number().positive().gt(0)
|
||||||
|
}),
|
||||||
|
auth: z
|
||||||
|
.object({
|
||||||
|
window_minutes: z.number().positive().gt(0),
|
||||||
|
max_requests: z.number().positive().gt(0)
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
}),
|
}),
|
||||||
email: z
|
email: z
|
||||||
.object({
|
.object({
|
||||||
|
@ -51,7 +59,7 @@ const environmentSchema = z.object({
|
||||||
smtp_port: portSchema.optional(),
|
smtp_port: portSchema.optional(),
|
||||||
smtp_user: z.string().optional(),
|
smtp_user: z.string().optional(),
|
||||||
smtp_pass: z.string().optional(),
|
smtp_pass: z.string().optional(),
|
||||||
no_reply: z.string().email().optional(),
|
no_reply: z.string().email().optional()
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
flags: z
|
flags: z
|
||||||
|
@ -59,9 +67,9 @@ const environmentSchema = z.object({
|
||||||
allow_org_subdomain_changing: z.boolean().optional(),
|
allow_org_subdomain_changing: z.boolean().optional(),
|
||||||
require_email_verification: z.boolean().optional(),
|
require_email_verification: z.boolean().optional(),
|
||||||
disable_signup_without_invite: 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) => {
|
const loadConfig = (configPath: string) => {
|
||||||
|
@ -72,7 +80,7 @@ const loadConfig = (configPath: string) => {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Error loading configuration file: ${error.message}`,
|
`Error loading configuration file: ${error.message}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
|
@ -94,21 +102,21 @@ if (!environment) {
|
||||||
try {
|
try {
|
||||||
const exampleConfigContent = fs.readFileSync(
|
const exampleConfigContent = fs.readFileSync(
|
||||||
exampleConfigPath,
|
exampleConfigPath,
|
||||||
"utf8",
|
"utf8"
|
||||||
);
|
);
|
||||||
fs.writeFileSync(configFilePath1, exampleConfigContent, "utf8");
|
fs.writeFileSync(configFilePath1, exampleConfigContent, "utf8");
|
||||||
environment = loadConfig(configFilePath1);
|
environment = loadConfig(configFilePath1);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Error creating configuration file from example: ${error.message}`,
|
`Error creating configuration file from example: ${error.message}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"No configuration file found and no example configuration available",
|
"No configuration file found and no example configuration available"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,11 @@ import { resourceAccessToken } from "@server/db/schema";
|
||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import db from "@server/db";
|
import db from "@server/db";
|
||||||
|
|
||||||
const deleteAccessTokenParamsSchema = z.object({
|
const deleteAccessTokenParamsSchema = z
|
||||||
|
.object({
|
||||||
accessTokenId: z.string()
|
accessTokenId: z.string()
|
||||||
});
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export async function deleteAccessToken(
|
export async function deleteAccessToken(
|
||||||
req: Request,
|
req: Request,
|
||||||
|
|
|
@ -5,7 +5,11 @@ import {
|
||||||
SESSION_COOKIE_EXPIRES
|
SESSION_COOKIE_EXPIRES
|
||||||
} from "@server/auth";
|
} from "@server/auth";
|
||||||
import db from "@server/db";
|
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 HttpCode from "@server/types/HttpCode";
|
||||||
import response from "@server/utils/response";
|
import response from "@server/utils/response";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
|
@ -16,17 +20,27 @@ import { fromError } from "zod-validation-error";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { createDate, TimeSpan } from "oslo";
|
import { createDate, TimeSpan } from "oslo";
|
||||||
|
|
||||||
export const generateAccessTokenBodySchema = z.object({
|
export const generateAccessTokenBodySchema = z
|
||||||
|
.object({
|
||||||
validForSeconds: z.number().int().positive().optional(), // seconds
|
validForSeconds: z.number().int().positive().optional(), // seconds
|
||||||
title: z.string().optional(),
|
title: z.string().optional(),
|
||||||
description: z.string().optional()
|
description: z.string().optional()
|
||||||
});
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export const generateAccssTokenParamsSchema = z.object({
|
export const generateAccssTokenParamsSchema = z
|
||||||
resourceId: z.string().transform(Number).pipe(z.number().int().positive())
|
.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(
|
export async function generateAccessToken(
|
||||||
req: Request,
|
req: Request,
|
||||||
|
@ -77,25 +91,38 @@ export async function generateAccessToken(
|
||||||
|
|
||||||
const token = generateIdFromEntropySize(25);
|
const token = generateIdFromEntropySize(25);
|
||||||
|
|
||||||
// const tokenHash = await hash(token, {
|
const tokenHash = await hash(token, {
|
||||||
// memoryCost: 19456,
|
memoryCost: 19456,
|
||||||
// timeCost: 2,
|
timeCost: 2,
|
||||||
// outputLen: 32,
|
outputLen: 32,
|
||||||
// parallelism: 1
|
parallelism: 1
|
||||||
// });
|
});
|
||||||
|
|
||||||
const id = generateId(15);
|
const id = generateId(15);
|
||||||
const [result] = await db.insert(resourceAccessToken).values({
|
const [result] = await db
|
||||||
|
.insert(resourceAccessToken)
|
||||||
|
.values({
|
||||||
accessTokenId: id,
|
accessTokenId: id,
|
||||||
orgId: resource.orgId,
|
orgId: resource.orgId,
|
||||||
resourceId,
|
resourceId,
|
||||||
tokenHash: token,
|
tokenHash,
|
||||||
expiresAt: expiresAt || null,
|
expiresAt: expiresAt || null,
|
||||||
sessionLength: sessionLength,
|
sessionLength: sessionLength,
|
||||||
title: title || null,
|
title: title || null,
|
||||||
description: description || null,
|
description: description || null,
|
||||||
createdAt: new Date().getTime()
|
createdAt: new Date().getTime()
|
||||||
}).returning();
|
})
|
||||||
|
.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) {
|
if (!result) {
|
||||||
return next(
|
return next(
|
||||||
|
@ -107,7 +134,7 @@ export async function generateAccessToken(
|
||||||
}
|
}
|
||||||
|
|
||||||
return response<GenerateAccessTokenResponse>(res, {
|
return response<GenerateAccessTokenResponse>(res, {
|
||||||
data: result,
|
data: { ...result, accessToken: token },
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Resource access token generated successfully",
|
message: "Resource access token generated successfully",
|
||||||
|
|
|
@ -23,6 +23,7 @@ const listAccessTokensParamsSchema = z
|
||||||
.pipe(z.number().int().positive().optional()),
|
.pipe(z.number().int().positive().optional()),
|
||||||
orgId: z.string().optional()
|
orgId: z.string().optional()
|
||||||
})
|
})
|
||||||
|
.strict()
|
||||||
.refine((data) => !!data.resourceId !== !!data.orgId, {
|
.refine((data) => !!data.resourceId !== !!data.orgId, {
|
||||||
message: "Either resourceId or orgId must be provided, but not both"
|
message: "Either resourceId or orgId must be provided, but not both"
|
||||||
});
|
});
|
||||||
|
@ -65,7 +66,10 @@ function queryAccessTokens(
|
||||||
return db
|
return db
|
||||||
.select(cols)
|
.select(cols)
|
||||||
.from(resourceAccessToken)
|
.from(resourceAccessToken)
|
||||||
.leftJoin(resources, eq(resourceAccessToken.resourceId, resources.resourceId))
|
.leftJoin(
|
||||||
|
resources,
|
||||||
|
eq(resourceAccessToken.resourceId, resources.resourceId)
|
||||||
|
)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
inArray(
|
inArray(
|
||||||
|
@ -83,7 +87,10 @@ function queryAccessTokens(
|
||||||
return db
|
return db
|
||||||
.select(cols)
|
.select(cols)
|
||||||
.from(resourceAccessToken)
|
.from(resourceAccessToken)
|
||||||
.leftJoin(resources, eq(resourceAccessToken.resourceId, resources.resourceId))
|
.leftJoin(
|
||||||
|
resources,
|
||||||
|
eq(resourceAccessToken.resourceId, resources.resourceId)
|
||||||
|
)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
inArray(
|
inArray(
|
||||||
|
|
|
@ -11,12 +11,13 @@ import { response } from "@server/utils";
|
||||||
import { hashPassword, verifyPassword } from "@server/auth/password";
|
import { hashPassword, verifyPassword } from "@server/auth/password";
|
||||||
import { verifyTotpCode } from "@server/auth/2fa";
|
import { verifyTotpCode } from "@server/auth/2fa";
|
||||||
import { passwordSchema } from "@server/auth/passwordSchema";
|
import { passwordSchema } from "@server/auth/passwordSchema";
|
||||||
|
import logger from "@server/logger";
|
||||||
|
|
||||||
export const changePasswordBody = z.object({
|
export const changePasswordBody = z.object({
|
||||||
oldPassword: z.string(),
|
oldPassword: z.string(),
|
||||||
newPassword: passwordSchema,
|
newPassword: passwordSchema,
|
||||||
code: z.string().optional(),
|
code: z.string().optional(),
|
||||||
});
|
}).strict();
|
||||||
|
|
||||||
export type ChangePasswordBody = z.infer<typeof changePasswordBody>;
|
export type ChangePasswordBody = z.infer<typeof changePasswordBody>;
|
||||||
|
|
||||||
|
@ -108,6 +109,7 @@ export async function changePassword(
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
HttpCode.INTERNAL_SERVER_ERROR,
|
||||||
|
|
|
@ -5,11 +5,12 @@ import { fromError } from "zod-validation-error";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import { response } from "@server/utils";
|
import { response } from "@server/utils";
|
||||||
import { validateResourceSessionToken } from "@server/auth/resource";
|
import { validateResourceSessionToken } from "@server/auth/resource";
|
||||||
|
import logger from "@server/logger";
|
||||||
|
|
||||||
export const params = z.object({
|
export const params = z.object({
|
||||||
token: z.string(),
|
token: z.string(),
|
||||||
resourceId: z.string().transform(Number).pipe(z.number().int().positive()),
|
resourceId: z.string().transform(Number).pipe(z.number().int().positive()),
|
||||||
});
|
}).strict();
|
||||||
|
|
||||||
export type CheckResourceSessionParams = z.infer<typeof params>;
|
export type CheckResourceSessionParams = z.infer<typeof params>;
|
||||||
|
|
||||||
|
@ -54,6 +55,7 @@ export async function checkResourceSession(
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
logger.error(e);
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
HttpCode.INTERNAL_SERVER_ERROR,
|
||||||
|
|
|
@ -10,11 +10,12 @@ import { eq } from "drizzle-orm";
|
||||||
import { response } from "@server/utils";
|
import { response } from "@server/utils";
|
||||||
import { verifyPassword } from "@server/auth/password";
|
import { verifyPassword } from "@server/auth/password";
|
||||||
import { verifyTotpCode } from "@server/auth/2fa";
|
import { verifyTotpCode } from "@server/auth/2fa";
|
||||||
|
import logger from "@server/logger";
|
||||||
|
|
||||||
export const disable2faBody = z.object({
|
export const disable2faBody = z.object({
|
||||||
password: z.string(),
|
password: z.string(),
|
||||||
code: z.string().optional(),
|
code: z.string().optional(),
|
||||||
});
|
}).strict();
|
||||||
|
|
||||||
export type Disable2faBody = z.infer<typeof disable2faBody>;
|
export type Disable2faBody = z.infer<typeof disable2faBody>;
|
||||||
|
|
||||||
|
@ -100,6 +101,7 @@ export async function disable2fa(
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
HttpCode.INTERNAL_SERVER_ERROR,
|
||||||
|
|
|
@ -22,7 +22,7 @@ export const loginBodySchema = z.object({
|
||||||
email: z.string().email(),
|
email: z.string().email(),
|
||||||
password: z.string(),
|
password: z.string(),
|
||||||
code: z.string().optional(),
|
code: z.string().optional(),
|
||||||
});
|
}).strict();
|
||||||
|
|
||||||
export type LoginBody = z.infer<typeof loginBodySchema>;
|
export type LoginBody = z.infer<typeof loginBodySchema>;
|
||||||
|
|
||||||
|
@ -151,6 +151,7 @@ export async function login(
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
logger.error(e);
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
HttpCode.INTERNAL_SERVER_ERROR,
|
||||||
|
|
|
@ -6,13 +6,13 @@ import logger from "@server/logger";
|
||||||
import {
|
import {
|
||||||
createBlankSessionTokenCookie,
|
createBlankSessionTokenCookie,
|
||||||
invalidateSession,
|
invalidateSession,
|
||||||
SESSION_COOKIE_NAME,
|
SESSION_COOKIE_NAME
|
||||||
} from "@server/auth";
|
} from "@server/auth";
|
||||||
|
|
||||||
export async function logout(
|
export async function logout(
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
next: NextFunction,
|
next: NextFunction
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
const sessionId = req.cookies[SESSION_COOKIE_NAME];
|
const sessionId = req.cookies[SESSION_COOKIE_NAME];
|
||||||
|
|
||||||
|
@ -20,8 +20,8 @@ export async function logout(
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
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,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Logged out successfully",
|
message: "Logged out successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Failed to log out", error);
|
logger.error(error);
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "Failed to log out")
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
|
||||||
"Failed to log out",
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,9 @@ import createHttpError from "http-errors";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import { response } from "@server/utils";
|
import { response } from "@server/utils";
|
||||||
import { User } from "@server/db/schema";
|
import { User } from "@server/db/schema";
|
||||||
import { sendEmailVerificationCode } from "./sendEmailVerificationCode";
|
import { sendEmailVerificationCode } from "../../auth/sendEmailVerificationCode";
|
||||||
import config from "@server/config";
|
import config from "@server/config";
|
||||||
|
import logger from "@server/logger";
|
||||||
|
|
||||||
export type RequestEmailVerificationCodeResponse = {
|
export type RequestEmailVerificationCodeResponse = {
|
||||||
codeSent: boolean;
|
codeSent: boolean;
|
||||||
|
@ -40,14 +41,15 @@ export async function requestEmailVerificationCode(
|
||||||
|
|
||||||
return response<RequestEmailVerificationCodeResponse>(res, {
|
return response<RequestEmailVerificationCodeResponse>(res, {
|
||||||
data: {
|
data: {
|
||||||
codeSent: true,
|
codeSent: true
|
||||||
},
|
},
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK,
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: `Email verification code sent to ${user.email}`,
|
message: `Email verification code sent to ${user.email}`
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
HttpCode.INTERNAL_SERVER_ERROR,
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { TimeSpan } from "oslo";
|
||||||
|
|
||||||
export const requestPasswordResetBody = z.object({
|
export const requestPasswordResetBody = z.object({
|
||||||
email: z.string().email(),
|
email: z.string().email(),
|
||||||
});
|
}).strict();
|
||||||
|
|
||||||
export type RequestPasswordResetBody = z.infer<typeof requestPasswordResetBody>;
|
export type RequestPasswordResetBody = z.infer<typeof requestPasswordResetBody>;
|
||||||
|
|
||||||
|
@ -87,6 +87,7 @@ export async function requestPasswordReset(
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
logger.error(e);
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
HttpCode.INTERNAL_SERVER_ERROR,
|
||||||
|
|
|
@ -12,10 +12,13 @@ import { eq } from "drizzle-orm";
|
||||||
import { verify } from "@node-rs/argon2";
|
import { verify } from "@node-rs/argon2";
|
||||||
import { createTOTPKeyURI } from "oslo/otp";
|
import { createTOTPKeyURI } from "oslo/otp";
|
||||||
import config from "@server/config";
|
import config from "@server/config";
|
||||||
|
import logger from "@server/logger";
|
||||||
|
|
||||||
export const requestTotpSecretBody = z.object({
|
export const requestTotpSecretBody = z
|
||||||
password: z.string(),
|
.object({
|
||||||
});
|
password: z.string()
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export type RequestTotpSecretBody = z.infer<typeof requestTotpSecretBody>;
|
export type RequestTotpSecretBody = z.infer<typeof requestTotpSecretBody>;
|
||||||
|
|
||||||
|
@ -26,7 +29,7 @@ export type RequestTotpSecretResponse = {
|
||||||
export async function requestTotpSecret(
|
export async function requestTotpSecret(
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
next: NextFunction,
|
next: NextFunction
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
const parsedBody = requestTotpSecretBody.safeParse(req.body);
|
const parsedBody = requestTotpSecretBody.safeParse(req.body);
|
||||||
|
|
||||||
|
@ -34,8 +37,8 @@ export async function requestTotpSecret(
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
fromError(parsedBody.error).toString(),
|
fromError(parsedBody.error).toString()
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +51,7 @@ export async function requestTotpSecret(
|
||||||
memoryCost: 19456,
|
memoryCost: 19456,
|
||||||
timeCost: 2,
|
timeCost: 2,
|
||||||
outputLen: 32,
|
outputLen: 32,
|
||||||
parallelism: 1,
|
parallelism: 1
|
||||||
});
|
});
|
||||||
if (!validPassword) {
|
if (!validPassword) {
|
||||||
return next(unauthorized());
|
return next(unauthorized());
|
||||||
|
@ -58,8 +61,8 @@ export async function requestTotpSecret(
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
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
|
await db
|
||||||
.update(users)
|
.update(users)
|
||||||
.set({
|
.set({
|
||||||
twoFactorSecret: secret,
|
twoFactorSecret: secret
|
||||||
})
|
})
|
||||||
.where(eq(users.userId, user.userId));
|
.where(eq(users.userId, user.userId));
|
||||||
|
|
||||||
return response<RequestTotpSecretResponse>(res, {
|
return response<RequestTotpSecretResponse>(res, {
|
||||||
data: {
|
data: {
|
||||||
secret: uri,
|
secret: uri
|
||||||
},
|
},
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "TOTP secret generated successfully",
|
message: "TOTP secret generated successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
HttpCode.INTERNAL_SERVER_ERROR,
|
||||||
"Failed to generate TOTP secret",
|
"Failed to generate TOTP secret"
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,12 +14,15 @@ import { passwordSchema } from "@server/auth/passwordSchema";
|
||||||
import { encodeHex } from "oslo/encoding";
|
import { encodeHex } from "oslo/encoding";
|
||||||
import { isWithinExpirationDate } from "oslo";
|
import { isWithinExpirationDate } from "oslo";
|
||||||
import { invalidateAllSessions } from "@server/auth";
|
import { invalidateAllSessions } from "@server/auth";
|
||||||
|
import logger from "@server/logger";
|
||||||
|
|
||||||
export const resetPasswordBody = z.object({
|
export const resetPasswordBody = z
|
||||||
|
.object({
|
||||||
token: z.string(),
|
token: z.string(),
|
||||||
newPassword: passwordSchema,
|
newPassword: passwordSchema,
|
||||||
code: z.string().optional(),
|
code: z.string().optional()
|
||||||
});
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export type ResetPasswordBody = z.infer<typeof resetPasswordBody>;
|
export type ResetPasswordBody = z.infer<typeof resetPasswordBody>;
|
||||||
|
|
||||||
|
@ -30,7 +33,7 @@ export type ResetPasswordResponse = {
|
||||||
export async function resetPassword(
|
export async function resetPassword(
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
next: NextFunction,
|
next: NextFunction
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
const parsedBody = resetPasswordBody.safeParse(req.body);
|
const parsedBody = resetPasswordBody.safeParse(req.body);
|
||||||
|
|
||||||
|
@ -38,8 +41,8 @@ export async function resetPassword(
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
fromError(parsedBody.error).toString(),
|
fromError(parsedBody.error).toString()
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +50,7 @@ export async function resetPassword(
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const tokenHash = encodeHex(
|
const tokenHash = encodeHex(
|
||||||
await sha256(new TextEncoder().encode(token)),
|
await sha256(new TextEncoder().encode(token))
|
||||||
);
|
);
|
||||||
|
|
||||||
const resetRequest = await db
|
const resetRequest = await db
|
||||||
|
@ -63,8 +66,8 @@ export async function resetPassword(
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
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(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
HttpCode.INTERNAL_SERVER_ERROR,
|
||||||
"User not found",
|
"User not found"
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,22 +92,22 @@ export async function resetPassword(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Two-factor authentication required",
|
message: "Two-factor authentication required",
|
||||||
status: HttpCode.ACCEPTED,
|
status: HttpCode.ACCEPTED
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const validOTP = await verifyTotpCode(
|
const validOTP = await verifyTotpCode(
|
||||||
code!,
|
code!,
|
||||||
user[0].twoFactorSecret!,
|
user[0].twoFactorSecret!,
|
||||||
user[0].userId,
|
user[0].userId
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!validOTP) {
|
if (!validOTP) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
"Invalid two-factor authentication code",
|
"Invalid two-factor authentication code"
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,14 +132,15 @@ export async function resetPassword(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Password reset successfully",
|
message: "Password reset successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
logger.error(e);
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
HttpCode.INTERNAL_SERVER_ERROR,
|
||||||
"Failed to reset password",
|
"Failed to reset password"
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { fromError } from "zod-validation-error";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import response from "@server/utils/response";
|
import response from "@server/utils/response";
|
||||||
import { SqliteError } from "better-sqlite3";
|
import { SqliteError } from "better-sqlite3";
|
||||||
import { sendEmailVerificationCode } from "./sendEmailVerificationCode";
|
import { sendEmailVerificationCode } from "../../auth/sendEmailVerificationCode";
|
||||||
import { passwordSchema } from "@server/auth/passwordSchema";
|
import { passwordSchema } from "@server/auth/passwordSchema";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
|
@ -20,6 +20,7 @@ import {
|
||||||
} from "@server/auth";
|
} from "@server/auth";
|
||||||
import { ActionsEnum } from "@server/auth/actions";
|
import { ActionsEnum } from "@server/auth/actions";
|
||||||
import config from "@server/config";
|
import config from "@server/config";
|
||||||
|
import logger from "@server/logger";
|
||||||
|
|
||||||
export const signupBodySchema = z.object({
|
export const signupBodySchema = z.object({
|
||||||
email: z.string().email(),
|
email: z.string().email(),
|
||||||
|
@ -153,6 +154,7 @@ export async function signup(
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
logger.error(e);
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
HttpCode.INTERNAL_SERVER_ERROR,
|
||||||
|
|
|
@ -9,10 +9,13 @@ import { User, emailVerificationCodes, users } from "@server/db/schema";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { isWithinExpirationDate } from "oslo";
|
import { isWithinExpirationDate } from "oslo";
|
||||||
import config from "@server/config";
|
import config from "@server/config";
|
||||||
|
import logger from "@server/logger";
|
||||||
|
|
||||||
export const verifyEmailBody = z.object({
|
export const verifyEmailBody = z
|
||||||
code: z.string(),
|
.object({
|
||||||
});
|
code: z.string()
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export type VerifyEmailBody = z.infer<typeof verifyEmailBody>;
|
export type VerifyEmailBody = z.infer<typeof verifyEmailBody>;
|
||||||
|
|
||||||
|
@ -66,7 +69,7 @@ export async function verifyEmail(
|
||||||
await db
|
await db
|
||||||
.update(users)
|
.update(users)
|
||||||
.set({
|
.set({
|
||||||
emailVerified: true,
|
emailVerified: true
|
||||||
})
|
})
|
||||||
.where(eq(users.userId, user.userId));
|
.where(eq(users.userId, user.userId));
|
||||||
} else {
|
} else {
|
||||||
|
@ -84,10 +87,11 @@ export async function verifyEmail(
|
||||||
message: "Email verified",
|
message: "Email verified",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK,
|
||||||
data: {
|
data: {
|
||||||
valid,
|
valid
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
HttpCode.INTERNAL_SERVER_ERROR,
|
||||||
|
|
|
@ -10,10 +10,13 @@ import { eq } from "drizzle-orm";
|
||||||
import { alphabet, generateRandomString } from "oslo/crypto";
|
import { alphabet, generateRandomString } from "oslo/crypto";
|
||||||
import { hashPassword } from "@server/auth/password";
|
import { hashPassword } from "@server/auth/password";
|
||||||
import { verifyTotpCode } from "@server/auth/2fa";
|
import { verifyTotpCode } from "@server/auth/2fa";
|
||||||
|
import logger from "@server/logger";
|
||||||
|
|
||||||
export const verifyTotpBody = z.object({
|
export const verifyTotpBody = z
|
||||||
code: z.string(),
|
.object({
|
||||||
});
|
code: z.string()
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export type VerifyTotpBody = z.infer<typeof verifyTotpBody>;
|
export type VerifyTotpBody = z.infer<typeof verifyTotpBody>;
|
||||||
|
|
||||||
|
@ -82,7 +85,7 @@ export async function verifyTotp(
|
||||||
|
|
||||||
await db.insert(twoFactorBackupCodes).values({
|
await db.insert(twoFactorBackupCodes).values({
|
||||||
userId: user.userId,
|
userId: user.userId,
|
||||||
codeHash: hash,
|
codeHash: hash
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,16 +95,17 @@ export async function verifyTotp(
|
||||||
return response<VerifyTotpResponse>(res, {
|
return response<VerifyTotpResponse>(res, {
|
||||||
data: {
|
data: {
|
||||||
valid,
|
valid,
|
||||||
...(valid && codes ? { backupCodes: codes } : {}),
|
...(valid && codes ? { backupCodes: codes } : {})
|
||||||
},
|
},
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: valid
|
message: valid
|
||||||
? "Code is valid. Two-factor is now enabled"
|
? "Code is valid. Two-factor is now enabled"
|
||||||
: "Code is invalid",
|
: "Code is invalid",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
HttpCode.INTERNAL_SERVER_ERROR,
|
||||||
|
|
|
@ -139,39 +139,36 @@ export async function verifyResourceSession(
|
||||||
);
|
);
|
||||||
|
|
||||||
if (resourceSession) {
|
if (resourceSession) {
|
||||||
|
if (pincode && resourceSession.pincodeId) {
|
||||||
|
logger.debug(
|
||||||
|
"Resource allowed because pincode session is valid"
|
||||||
|
);
|
||||||
return allowed(res);
|
return allowed(res);
|
||||||
|
}
|
||||||
|
|
||||||
// Might not be needed
|
if (password && resourceSession.passwordId) {
|
||||||
// if (pincode && resourceSession.pincodeId) {
|
logger.debug(
|
||||||
// logger.debug(
|
"Resource allowed because password session is valid"
|
||||||
// "Resource allowed because pincode session is valid"
|
);
|
||||||
// );
|
return allowed(res);
|
||||||
// return allowed(res);
|
}
|
||||||
// }
|
|
||||||
//
|
if (
|
||||||
// if (password && resourceSession.passwordId) {
|
resource.emailWhitelistEnabled &&
|
||||||
// logger.debug(
|
resourceSession.whitelistId
|
||||||
// "Resource allowed because password session is valid"
|
) {
|
||||||
// );
|
logger.debug(
|
||||||
// return allowed(res);
|
"Resource allowed because whitelist session is valid"
|
||||||
// }
|
);
|
||||||
//
|
return allowed(res);
|
||||||
// if (
|
}
|
||||||
// resource.emailWhitelistEnabled &&
|
|
||||||
// resourceSession.whitelistId
|
if (resourceSession.accessTokenId) {
|
||||||
// ) {
|
logger.debug(
|
||||||
// logger.debug(
|
"Resource allowed because access token session is valid"
|
||||||
// "Resource allowed because whitelist session is valid"
|
);
|
||||||
// );
|
return allowed(res);
|
||||||
// return allowed(res);
|
}
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (resourceSession.accessTokenId) {
|
|
||||||
// logger.debug(
|
|
||||||
// "Resource allowed because access token session is valid"
|
|
||||||
// );
|
|
||||||
// return allowed(res);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
|
import config from "@server/config";
|
||||||
import * as site from "./site";
|
import * as site from "./site";
|
||||||
import * as org from "./org";
|
import * as org from "./org";
|
||||||
import * as resource from "./resource";
|
import * as resource from "./resource";
|
||||||
|
@ -419,8 +420,12 @@ export const authRouter = Router();
|
||||||
unauthenticated.use("/auth", authRouter);
|
unauthenticated.use("/auth", authRouter);
|
||||||
authRouter.use(
|
authRouter.use(
|
||||||
rateLimitMiddleware({
|
rateLimitMiddleware({
|
||||||
windowMin: 10,
|
windowMin:
|
||||||
max: 75,
|
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"
|
type: "IP_AND_PATH"
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
|
@ -9,9 +9,11 @@ import createHttpError from "http-errors";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
|
|
||||||
const getOrgSchema = z.object({
|
const getOrgSchema = z
|
||||||
orgId: z.string(),
|
.object({
|
||||||
});
|
orgId: z.string()
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export async function checkId(
|
export async function checkId(
|
||||||
req: Request,
|
req: Request,
|
||||||
|
@ -43,7 +45,7 @@ export async function checkId(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Organization ID already exists",
|
message: "Organization ID already exists",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +54,7 @@ export async function checkId(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Organization ID is available",
|
message: "Organization ID is available",
|
||||||
status: HttpCode.NOT_FOUND,
|
status: HttpCode.NOT_FOUND
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -10,9 +10,11 @@ import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
|
|
||||||
const deleteOrgSchema = z.object({
|
const deleteOrgSchema = z
|
||||||
orgId: z.string(),
|
.object({
|
||||||
});
|
orgId: z.string()
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export async function deleteOrg(
|
export async function deleteOrg(
|
||||||
req: Request,
|
req: Request,
|
||||||
|
@ -65,7 +67,7 @@ export async function deleteOrg(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Organization deleted successfully",
|
message: "Organization deleted successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -8,9 +8,11 @@ import HttpCode from "@server/types/HttpCode";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
|
|
||||||
const getOrgSchema = z.object({
|
const getOrgSchema = z
|
||||||
orgId: z.string(),
|
.object({
|
||||||
});
|
orgId: z.string()
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export type GetOrgResponse = {
|
export type GetOrgResponse = {
|
||||||
org: Org;
|
org: Org;
|
||||||
|
@ -51,12 +53,12 @@ export async function getOrg(
|
||||||
|
|
||||||
return response<GetOrgResponse>(res, {
|
return response<GetOrgResponse>(res, {
|
||||||
data: {
|
data: {
|
||||||
org: org[0],
|
org: org[0]
|
||||||
},
|
},
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Organization retrieved successfully",
|
message: "Organization retrieved successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -9,18 +9,20 @@ import createHttpError from "http-errors";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
|
|
||||||
const updateOrgParamsSchema = z.object({
|
const updateOrgParamsSchema = z
|
||||||
orgId: z.string(),
|
.object({
|
||||||
});
|
orgId: z.string()
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
const updateOrgBodySchema = z
|
const updateOrgBodySchema = z
|
||||||
.object({
|
.object({
|
||||||
name: z.string().min(1).max(255).optional(),
|
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()
|
.strict()
|
||||||
.refine((data) => Object.keys(data).length > 0, {
|
.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(
|
export async function updateOrg(
|
||||||
|
@ -72,7 +74,7 @@ export async function updateOrg(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Organization updated successfully",
|
message: "Organization updated successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -17,13 +17,21 @@ import logger from "@server/logger";
|
||||||
import { verify } from "@node-rs/argon2";
|
import { verify } from "@node-rs/argon2";
|
||||||
import { isWithinExpirationDate } from "oslo";
|
import { isWithinExpirationDate } from "oslo";
|
||||||
|
|
||||||
const authWithAccessTokenBodySchema = z.object({
|
const authWithAccessTokenBodySchema = z
|
||||||
accessToken: z.string()
|
.object({
|
||||||
});
|
accessToken: z.string(),
|
||||||
|
accessTokenId: z.string()
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
const authWithAccessTokenParamsSchema = z.object({
|
const authWithAccessTokenParamsSchema = z
|
||||||
resourceId: z.string().transform(Number).pipe(z.number().int().positive())
|
.object({
|
||||||
});
|
resourceId: z
|
||||||
|
.string()
|
||||||
|
.transform(Number)
|
||||||
|
.pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export type AuthWithAccessTokenResponse = {
|
export type AuthWithAccessTokenResponse = {
|
||||||
session?: string;
|
session?: string;
|
||||||
|
@ -57,9 +65,7 @@ export async function authWithAccessToken(
|
||||||
}
|
}
|
||||||
|
|
||||||
const { resourceId } = parsedParams.data;
|
const { resourceId } = parsedParams.data;
|
||||||
const { accessToken: at } = parsedBody.data;
|
const { accessToken, accessTokenId } = parsedBody.data;
|
||||||
|
|
||||||
const [accessTokenId, accessToken] = at.split(".");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [result] = await db
|
const [result] = await db
|
||||||
|
@ -86,7 +92,7 @@ export async function authWithAccessToken(
|
||||||
HttpCode.UNAUTHORIZED,
|
HttpCode.UNAUTHORIZED,
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
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, {
|
const validCode = await verify(tokenItem.tokenHash, accessToken, {
|
||||||
// memoryCost: 19456,
|
memoryCost: 19456,
|
||||||
// timeCost: 2,
|
timeCost: 2,
|
||||||
// outputLen: 32,
|
outputLen: 32,
|
||||||
// parallelism: 1
|
parallelism: 1
|
||||||
// });
|
});
|
||||||
logger.debug(`${accessToken} ${tokenItem.tokenHash}`)
|
|
||||||
const validCode = accessToken === tokenItem.tokenHash;
|
|
||||||
|
|
||||||
if (!validCode) {
|
if (!validCode) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(HttpCode.UNAUTHORIZED, "Invalid access token")
|
createHttpError(HttpCode.UNAUTHORIZED, "Invalid access token")
|
||||||
|
|
|
@ -14,14 +14,22 @@ import {
|
||||||
serializeResourceSessionCookie
|
serializeResourceSessionCookie
|
||||||
} from "@server/auth/resource";
|
} from "@server/auth/resource";
|
||||||
import config from "@server/config";
|
import config from "@server/config";
|
||||||
|
import logger from "@server/logger";
|
||||||
|
|
||||||
export const authWithPasswordBodySchema = z.object({
|
export const authWithPasswordBodySchema = z
|
||||||
|
.object({
|
||||||
password: z.string()
|
password: z.string()
|
||||||
});
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export const authWithPasswordParamsSchema = z.object({
|
export const authWithPasswordParamsSchema = z
|
||||||
resourceId: z.string().transform(Number).pipe(z.number().int().positive())
|
.object({
|
||||||
});
|
resourceId: z
|
||||||
|
.string()
|
||||||
|
.transform(Number)
|
||||||
|
.pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export type AuthWithPasswordResponse = {
|
export type AuthWithPasswordResponse = {
|
||||||
session?: string;
|
session?: string;
|
||||||
|
@ -120,10 +128,7 @@ export async function authWithPassword(
|
||||||
passwordId: definedPassword.passwordId
|
passwordId: definedPassword.passwordId
|
||||||
});
|
});
|
||||||
const cookieName = `${config.server.resource_session_cookie_name}_${resource.resourceId}`;
|
const cookieName = `${config.server.resource_session_cookie_name}_${resource.resourceId}`;
|
||||||
const cookie = serializeResourceSessionCookie(
|
const cookie = serializeResourceSessionCookie(cookieName, token);
|
||||||
cookieName,
|
|
||||||
token,
|
|
||||||
);
|
|
||||||
res.appendHeader("Set-Cookie", cookie);
|
res.appendHeader("Set-Cookie", cookie);
|
||||||
|
|
||||||
return response<AuthWithPasswordResponse>(res, {
|
return response<AuthWithPasswordResponse>(res, {
|
||||||
|
@ -136,6 +141,7 @@ export async function authWithPassword(
|
||||||
status: HttpCode.OK
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
logger.error(e);
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
HttpCode.INTERNAL_SERVER_ERROR,
|
||||||
|
|
|
@ -24,13 +24,20 @@ import config from "@server/config";
|
||||||
import { AuthWithPasswordResponse } from "./authWithPassword";
|
import { AuthWithPasswordResponse } from "./authWithPassword";
|
||||||
import { isValidOtp, sendResourceOtpEmail } from "@server/auth/resourceOtp";
|
import { isValidOtp, sendResourceOtpEmail } from "@server/auth/resourceOtp";
|
||||||
|
|
||||||
export const authWithPincodeBodySchema = z.object({
|
export const authWithPincodeBodySchema = z
|
||||||
|
.object({
|
||||||
pincode: z.string()
|
pincode: z.string()
|
||||||
});
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export const authWithPincodeParamsSchema = z.object({
|
export const authWithPincodeParamsSchema = z
|
||||||
resourceId: z.string().transform(Number).pipe(z.number().int().positive())
|
.object({
|
||||||
});
|
resourceId: z
|
||||||
|
.string()
|
||||||
|
.transform(Number)
|
||||||
|
.pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export type AuthWithPincodeResponse = {
|
export type AuthWithPincodeResponse = {
|
||||||
session?: string;
|
session?: string;
|
||||||
|
@ -128,10 +135,7 @@ export async function authWithPincode(
|
||||||
pincodeId: definedPincode.pincodeId
|
pincodeId: definedPincode.pincodeId
|
||||||
});
|
});
|
||||||
const cookieName = `${config.server.resource_session_cookie_name}_${resource.resourceId}`;
|
const cookieName = `${config.server.resource_session_cookie_name}_${resource.resourceId}`;
|
||||||
const cookie = serializeResourceSessionCookie(
|
const cookie = serializeResourceSessionCookie(cookieName, token);
|
||||||
cookieName,
|
|
||||||
token,
|
|
||||||
);
|
|
||||||
res.appendHeader("Set-Cookie", cookie);
|
res.appendHeader("Set-Cookie", cookie);
|
||||||
|
|
||||||
return response<AuthWithPincodeResponse>(res, {
|
return response<AuthWithPincodeResponse>(res, {
|
||||||
|
|
|
@ -22,14 +22,21 @@ import config from "@server/config";
|
||||||
import { isValidOtp, sendResourceOtpEmail } from "@server/auth/resourceOtp";
|
import { isValidOtp, sendResourceOtpEmail } from "@server/auth/resourceOtp";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
|
|
||||||
const authWithWhitelistBodySchema = z.object({
|
const authWithWhitelistBodySchema = z
|
||||||
|
.object({
|
||||||
email: z.string().email(),
|
email: z.string().email(),
|
||||||
otp: z.string().optional()
|
otp: z.string().optional()
|
||||||
});
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
const authWithWhitelistParamsSchema = z.object({
|
const authWithWhitelistParamsSchema = z
|
||||||
resourceId: z.string().transform(Number).pipe(z.number().int().positive())
|
.object({
|
||||||
});
|
resourceId: z
|
||||||
|
.string()
|
||||||
|
.transform(Number)
|
||||||
|
.pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export type AuthWithWhitelistResponse = {
|
export type AuthWithWhitelistResponse = {
|
||||||
otpSent?: boolean;
|
otpSent?: boolean;
|
||||||
|
@ -171,10 +178,7 @@ export async function authWithWhitelist(
|
||||||
whitelistId: whitelistedEmail.whitelistId
|
whitelistId: whitelistedEmail.whitelistId
|
||||||
});
|
});
|
||||||
const cookieName = `${config.server.resource_session_cookie_name}_${resource.resourceId}`;
|
const cookieName = `${config.server.resource_session_cookie_name}_${resource.resourceId}`;
|
||||||
const cookie = serializeResourceSessionCookie(
|
const cookie = serializeResourceSessionCookie(cookieName, token);
|
||||||
cookieName,
|
|
||||||
token,
|
|
||||||
);
|
|
||||||
res.appendHeader("Set-Cookie", cookie);
|
res.appendHeader("Set-Cookie", cookie);
|
||||||
|
|
||||||
return response<AuthWithWhitelistResponse>(res, {
|
return response<AuthWithWhitelistResponse>(res, {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { SqliteError } from "better-sqlite3";
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
|
@ -7,7 +8,7 @@ import {
|
||||||
resources,
|
resources,
|
||||||
roleResources,
|
roleResources,
|
||||||
roles,
|
roles,
|
||||||
userResources,
|
userResources
|
||||||
} from "@server/db/schema";
|
} from "@server/db/schema";
|
||||||
import response from "@server/utils/response";
|
import response from "@server/utils/response";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
|
@ -16,16 +17,19 @@ import { eq, and } from "drizzle-orm";
|
||||||
import stoi from "@server/utils/stoi";
|
import stoi from "@server/utils/stoi";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { subdomainSchema } from "@server/schemas/subdomainSchema";
|
import { subdomainSchema } from "@server/schemas/subdomainSchema";
|
||||||
|
import logger from "@server/logger";
|
||||||
|
|
||||||
const createResourceParamsSchema = z.object({
|
const createResourceParamsSchema = z
|
||||||
|
.object({
|
||||||
siteId: z.string().transform(stoi).pipe(z.number().int().positive()),
|
siteId: z.string().transform(stoi).pipe(z.number().int().positive()),
|
||||||
orgId: z.string(),
|
orgId: z.string()
|
||||||
});
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
const createResourceSchema = z
|
const createResourceSchema = z
|
||||||
.object({
|
.object({
|
||||||
name: z.string().min(1).max(255),
|
name: z.string().min(1).max(255),
|
||||||
subdomain: subdomainSchema,
|
subdomain: subdomainSchema
|
||||||
})
|
})
|
||||||
.strict();
|
.strict();
|
||||||
|
|
||||||
|
@ -94,7 +98,7 @@ export async function createResource(
|
||||||
orgId,
|
orgId,
|
||||||
name,
|
name,
|
||||||
subdomain,
|
subdomain,
|
||||||
ssl: true,
|
ssl: true
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
@ -112,14 +116,14 @@ export async function createResource(
|
||||||
|
|
||||||
await db.insert(roleResources).values({
|
await db.insert(roleResources).values({
|
||||||
roleId: adminRole[0].roleId,
|
roleId: adminRole[0].roleId,
|
||||||
resourceId: newResource[0].resourceId,
|
resourceId: newResource[0].resourceId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (req.userOrgRoleId != adminRole[0].roleId) {
|
if (req.userOrgRoleId != adminRole[0].roleId) {
|
||||||
// make sure the user can access the resource
|
// make sure the user can access the resource
|
||||||
await db.insert(userResources).values({
|
await db.insert(userResources).values({
|
||||||
userId: req.user?.userId!,
|
userId: req.user?.userId!,
|
||||||
resourceId: newResource[0].resourceId,
|
resourceId: newResource[0].resourceId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,9 +132,22 @@ export async function createResource(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Resource created successfully",
|
message: "Resource created successfully",
|
||||||
status: HttpCode.CREATED,
|
status: HttpCode.CREATED
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} 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(
|
return next(
|
||||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||||
);
|
);
|
||||||
|
|
|
@ -12,9 +12,14 @@ import { addPeer } from "../gerbil/peers";
|
||||||
import { removeTargets } from "../newt/targets";
|
import { removeTargets } from "../newt/targets";
|
||||||
|
|
||||||
// Define Zod schema for request parameters validation
|
// Define Zod schema for request parameters validation
|
||||||
const deleteResourceSchema = z.object({
|
const deleteResourceSchema = z
|
||||||
resourceId: z.string().transform(Number).pipe(z.number().int().positive()),
|
.object({
|
||||||
});
|
resourceId: z
|
||||||
|
.string()
|
||||||
|
.transform(Number)
|
||||||
|
.pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export async function deleteResource(
|
export async function deleteResource(
|
||||||
req: Request,
|
req: Request,
|
||||||
|
@ -73,14 +78,14 @@ export async function deleteResource(
|
||||||
// TODO: is this all inefficient?
|
// TODO: is this all inefficient?
|
||||||
// Fetch resources for this site
|
// Fetch resources for this site
|
||||||
const resourcesRes = await db.query.resources.findMany({
|
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
|
// Fetch targets for all resources of this site
|
||||||
const targetIps = await Promise.all(
|
const targetIps = await Promise.all(
|
||||||
resourcesRes.map(async (resource) => {
|
resourcesRes.map(async (resource) => {
|
||||||
const targetsRes = await db.query.targets.findMany({
|
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`);
|
return targetsRes.map((target) => `${target.ip}/32`);
|
||||||
})
|
})
|
||||||
|
@ -88,7 +93,7 @@ export async function deleteResource(
|
||||||
|
|
||||||
await addPeer(site.exitNodeId!, {
|
await addPeer(site.exitNodeId!, {
|
||||||
publicKey: site.pubKey,
|
publicKey: site.pubKey,
|
||||||
allowedIps: targetIps.flat(),
|
allowedIps: targetIps.flat()
|
||||||
});
|
});
|
||||||
} else if (site.type == "newt") {
|
} else if (site.type == "newt") {
|
||||||
// get the newt on the site by querying the newt table for siteId
|
// get the newt on the site by querying the newt table for siteId
|
||||||
|
@ -107,7 +112,7 @@ export async function deleteResource(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Resource deleted successfully",
|
message: "Resource deleted successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -7,10 +7,16 @@ import response from "@server/utils/response";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
|
import logger from "@server/logger";
|
||||||
|
|
||||||
const getResourceSchema = z.object({
|
const getResourceSchema = z
|
||||||
resourceId: z.string().transform(Number).pipe(z.number().int().positive()),
|
.object({
|
||||||
});
|
resourceId: z
|
||||||
|
.string()
|
||||||
|
.transform(Number)
|
||||||
|
.pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export type GetResourceResponse = Resource;
|
export type GetResourceResponse = Resource;
|
||||||
|
|
||||||
|
@ -52,9 +58,10 @@ export async function getResource(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Resource retrieved successfully",
|
message: "Resource retrieved successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
return next(
|
return next(
|
||||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,17 +4,23 @@ import { db } from "@server/db";
|
||||||
import {
|
import {
|
||||||
resourcePassword,
|
resourcePassword,
|
||||||
resourcePincode,
|
resourcePincode,
|
||||||
resources,
|
resources
|
||||||
} from "@server/db/schema";
|
} from "@server/db/schema";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import response from "@server/utils/response";
|
import response from "@server/utils/response";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
|
import logger from "@server/logger";
|
||||||
|
|
||||||
const getResourceAuthInfoSchema = z.object({
|
const getResourceAuthInfoSchema = z
|
||||||
resourceId: z.string().transform(Number).pipe(z.number().int().positive()),
|
.object({
|
||||||
});
|
resourceId: z
|
||||||
|
.string()
|
||||||
|
.transform(Number)
|
||||||
|
.pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export type GetResourceAuthInfoResponse = {
|
export type GetResourceAuthInfoResponse = {
|
||||||
resourceId: number;
|
resourceId: number;
|
||||||
|
@ -30,7 +36,7 @@ export type GetResourceAuthInfoResponse = {
|
||||||
export async function getResourceAuthInfo(
|
export async function getResourceAuthInfo(
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
next: NextFunction,
|
next: NextFunction
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const parsedParams = getResourceAuthInfoSchema.safeParse(req.params);
|
const parsedParams = getResourceAuthInfoSchema.safeParse(req.params);
|
||||||
|
@ -38,8 +44,8 @@ export async function getResourceAuthInfo(
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
fromError(parsedParams.error).toString(),
|
fromError(parsedParams.error).toString()
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,11 +56,11 @@ export async function getResourceAuthInfo(
|
||||||
.from(resources)
|
.from(resources)
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
resourcePincode,
|
resourcePincode,
|
||||||
eq(resourcePincode.resourceId, resources.resourceId),
|
eq(resourcePincode.resourceId, resources.resourceId)
|
||||||
)
|
)
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
resourcePassword,
|
resourcePassword,
|
||||||
eq(resourcePassword.resourceId, resources.resourceId),
|
eq(resourcePassword.resourceId, resources.resourceId)
|
||||||
)
|
)
|
||||||
.where(eq(resources.resourceId, resourceId))
|
.where(eq(resources.resourceId, resourceId))
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
@ -67,7 +73,7 @@ export async function getResourceAuthInfo(
|
||||||
|
|
||||||
if (!resource) {
|
if (!resource) {
|
||||||
return next(
|
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,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Resource auth info retrieved successfully",
|
message: "Resource auth info retrieved successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
|
||||||
"An error occurred",
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,14 @@ import createHttpError from "http-errors";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
|
|
||||||
const getResourceWhitelistSchema = z.object({
|
const getResourceWhitelistSchema = z
|
||||||
resourceId: z.string().transform(Number).pipe(z.number().int().positive())
|
.object({
|
||||||
});
|
resourceId: z
|
||||||
|
.string()
|
||||||
|
.transform(Number)
|
||||||
|
.pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
async function queryWhitelist(resourceId: number) {
|
async function queryWhitelist(resourceId: number) {
|
||||||
return await db
|
return await db
|
||||||
|
|
|
@ -9,9 +9,14 @@ import createHttpError from "http-errors";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
|
|
||||||
const listResourceRolesSchema = z.object({
|
const listResourceRolesSchema = z
|
||||||
resourceId: z.string().transform(Number).pipe(z.number().int().positive()),
|
.object({
|
||||||
});
|
resourceId: z
|
||||||
|
.string()
|
||||||
|
.transform(Number)
|
||||||
|
.pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
async function query(resourceId: number) {
|
async function query(resourceId: number) {
|
||||||
return await db
|
return await db
|
||||||
|
@ -19,7 +24,7 @@ async function query(resourceId: number) {
|
||||||
roleId: roles.roleId,
|
roleId: roles.roleId,
|
||||||
name: roles.name,
|
name: roles.name,
|
||||||
description: roles.description,
|
description: roles.description,
|
||||||
isAdmin: roles.isAdmin,
|
isAdmin: roles.isAdmin
|
||||||
})
|
})
|
||||||
.from(roleResources)
|
.from(roleResources)
|
||||||
.innerJoin(roles, eq(roleResources.roleId, roles.roleId))
|
.innerJoin(roles, eq(roleResources.roleId, roles.roleId))
|
||||||
|
@ -52,12 +57,12 @@ export async function listResourceRoles(
|
||||||
|
|
||||||
return response<ListResourceRolesResponse>(res, {
|
return response<ListResourceRolesResponse>(res, {
|
||||||
data: {
|
data: {
|
||||||
roles: resourceRolesList,
|
roles: resourceRolesList
|
||||||
},
|
},
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Resource roles retrieved successfully",
|
message: "Resource roles retrieved successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -9,15 +9,20 @@ import createHttpError from "http-errors";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
|
|
||||||
const listResourceUsersSchema = z.object({
|
const listResourceUsersSchema = z
|
||||||
resourceId: z.string().transform(Number).pipe(z.number().int().positive()),
|
.object({
|
||||||
});
|
resourceId: z
|
||||||
|
.string()
|
||||||
|
.transform(Number)
|
||||||
|
.pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
async function queryUsers(resourceId: number) {
|
async function queryUsers(resourceId: number) {
|
||||||
return await db
|
return await db
|
||||||
.select({
|
.select({
|
||||||
userId: userResources.userId,
|
userId: userResources.userId,
|
||||||
email: users.email,
|
email: users.email
|
||||||
})
|
})
|
||||||
.from(userResources)
|
.from(userResources)
|
||||||
.innerJoin(users, eq(userResources.userId, users.userId))
|
.innerJoin(users, eq(userResources.userId, users.userId))
|
||||||
|
@ -50,12 +55,12 @@ export async function listResourceUsers(
|
||||||
|
|
||||||
return response<ListResourceUsersResponse>(res, {
|
return response<ListResourceUsersResponse>(res, {
|
||||||
data: {
|
data: {
|
||||||
users: resourceUsersList,
|
users: resourceUsersList
|
||||||
},
|
},
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Resource users retrieved successfully",
|
message: "Resource users retrieved successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
userResources,
|
userResources,
|
||||||
roleResources,
|
roleResources,
|
||||||
resourcePassword,
|
resourcePassword,
|
||||||
resourcePincode,
|
resourcePincode
|
||||||
} from "@server/db/schema";
|
} from "@server/db/schema";
|
||||||
import response from "@server/utils/response";
|
import response from "@server/utils/response";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
|
@ -23,10 +23,11 @@ const listResourcesParamsSchema = z
|
||||||
.optional()
|
.optional()
|
||||||
.transform(stoi)
|
.transform(stoi)
|
||||||
.pipe(z.number().int().positive().optional()),
|
.pipe(z.number().int().positive().optional()),
|
||||||
orgId: z.string().optional(),
|
orgId: z.string().optional()
|
||||||
})
|
})
|
||||||
|
.strict()
|
||||||
.refine((data) => !!data.siteId !== !!data.orgId, {
|
.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({
|
const listResourcesSchema = z.object({
|
||||||
|
@ -42,13 +43,13 @@ const listResourcesSchema = z.object({
|
||||||
.optional()
|
.optional()
|
||||||
.default("0")
|
.default("0")
|
||||||
.transform(Number)
|
.transform(Number)
|
||||||
.pipe(z.number().int().nonnegative()),
|
.pipe(z.number().int().nonnegative())
|
||||||
});
|
});
|
||||||
|
|
||||||
function queryResources(
|
function queryResources(
|
||||||
accessibleResourceIds: number[],
|
accessibleResourceIds: number[],
|
||||||
siteId?: number,
|
siteId?: number,
|
||||||
orgId?: string,
|
orgId?: string
|
||||||
) {
|
) {
|
||||||
if (siteId) {
|
if (siteId) {
|
||||||
return db
|
return db
|
||||||
|
@ -68,17 +69,17 @@ function queryResources(
|
||||||
.leftJoin(sites, eq(resources.siteId, sites.siteId))
|
.leftJoin(sites, eq(resources.siteId, sites.siteId))
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
resourcePassword,
|
resourcePassword,
|
||||||
eq(resourcePassword.resourceId, resources.resourceId),
|
eq(resourcePassword.resourceId, resources.resourceId)
|
||||||
)
|
)
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
resourcePincode,
|
resourcePincode,
|
||||||
eq(resourcePincode.resourceId, resources.resourceId),
|
eq(resourcePincode.resourceId, resources.resourceId)
|
||||||
)
|
)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
inArray(resources.resourceId, accessibleResourceIds),
|
inArray(resources.resourceId, accessibleResourceIds),
|
||||||
eq(resources.siteId, siteId),
|
eq(resources.siteId, siteId)
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
} else if (orgId) {
|
} else if (orgId) {
|
||||||
return db
|
return db
|
||||||
|
@ -98,17 +99,17 @@ function queryResources(
|
||||||
.leftJoin(sites, eq(resources.siteId, sites.siteId))
|
.leftJoin(sites, eq(resources.siteId, sites.siteId))
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
resourcePassword,
|
resourcePassword,
|
||||||
eq(resourcePassword.resourceId, resources.resourceId),
|
eq(resourcePassword.resourceId, resources.resourceId)
|
||||||
)
|
)
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
resourcePincode,
|
resourcePincode,
|
||||||
eq(resourcePincode.resourceId, resources.resourceId),
|
eq(resourcePincode.resourceId, resources.resourceId)
|
||||||
)
|
)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
inArray(resources.resourceId, accessibleResourceIds),
|
inArray(resources.resourceId, accessibleResourceIds),
|
||||||
eq(resources.orgId, orgId),
|
eq(resources.orgId, orgId)
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,7 +122,7 @@ export type ListResourcesResponse = {
|
||||||
export async function listResources(
|
export async function listResources(
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
next: NextFunction,
|
next: NextFunction
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const parsedQuery = listResourcesSchema.safeParse(req.query);
|
const parsedQuery = listResourcesSchema.safeParse(req.query);
|
||||||
|
@ -129,8 +130,8 @@ export async function listResources(
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
parsedQuery.error.errors.map((e) => e.message).join(", "),
|
parsedQuery.error.errors.map((e) => e.message).join(", ")
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const { limit, offset } = parsedQuery.data;
|
const { limit, offset } = parsedQuery.data;
|
||||||
|
@ -140,8 +141,8 @@ export async function listResources(
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
parsedParams.error.errors.map((e) => e.message).join(", "),
|
parsedParams.error.errors.map((e) => e.message).join(", ")
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const { siteId, orgId } = parsedParams.data;
|
const { siteId, orgId } = parsedParams.data;
|
||||||
|
@ -150,29 +151,29 @@ export async function listResources(
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.FORBIDDEN,
|
HttpCode.FORBIDDEN,
|
||||||
"User does not have access to this organization",
|
"User does not have access to this organization"
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const accessibleResources = await db
|
const accessibleResources = await db
|
||||||
.select({
|
.select({
|
||||||
resourceId: sql<number>`COALESCE(${userResources.resourceId}, ${roleResources.resourceId})`,
|
resourceId: sql<number>`COALESCE(${userResources.resourceId}, ${roleResources.resourceId})`
|
||||||
})
|
})
|
||||||
.from(userResources)
|
.from(userResources)
|
||||||
.fullJoin(
|
.fullJoin(
|
||||||
roleResources,
|
roleResources,
|
||||||
eq(userResources.resourceId, roleResources.resourceId),
|
eq(userResources.resourceId, roleResources.resourceId)
|
||||||
)
|
)
|
||||||
.where(
|
.where(
|
||||||
or(
|
or(
|
||||||
eq(userResources.userId, req.user!.userId),
|
eq(userResources.userId, req.user!.userId),
|
||||||
eq(roleResources.roleId, req.userOrgRoleId!),
|
eq(roleResources.roleId, req.userOrgRoleId!)
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const accessibleResourceIds = accessibleResources.map(
|
const accessibleResourceIds = accessibleResources.map(
|
||||||
(resource) => resource.resourceId,
|
(resource) => resource.resourceId
|
||||||
);
|
);
|
||||||
|
|
||||||
let countQuery: any = db
|
let countQuery: any = db
|
||||||
|
@ -192,21 +193,18 @@ export async function listResources(
|
||||||
pagination: {
|
pagination: {
|
||||||
total: totalCount,
|
total: totalCount,
|
||||||
limit,
|
limit,
|
||||||
offset,
|
offset
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Resources retrieved successfully",
|
message: "Resources retrieved successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
|
||||||
"An error occurred",
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,14 +8,15 @@ import createHttpError from "http-errors";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { hash } from "@node-rs/argon2";
|
import { hash } from "@node-rs/argon2";
|
||||||
import { response } from "@server/utils";
|
import { response } from "@server/utils";
|
||||||
|
import logger from "@server/logger";
|
||||||
|
|
||||||
const setResourceAuthMethodsParamsSchema = z.object({
|
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
|
const setResourceAuthMethodsBodySchema = z
|
||||||
.object({
|
.object({
|
||||||
password: z.string().min(4).max(100).nullable(),
|
password: z.string().min(4).max(100).nullable()
|
||||||
})
|
})
|
||||||
.strict();
|
.strict();
|
||||||
|
|
||||||
|
@ -60,7 +61,7 @@ export async function setResourcePassword(
|
||||||
memoryCost: 19456,
|
memoryCost: 19456,
|
||||||
timeCost: 2,
|
timeCost: 2,
|
||||||
outputLen: 32,
|
outputLen: 32,
|
||||||
parallelism: 1,
|
parallelism: 1
|
||||||
});
|
});
|
||||||
|
|
||||||
await trx
|
await trx
|
||||||
|
@ -74,9 +75,10 @@ export async function setResourcePassword(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Resource password set successfully",
|
message: "Resource password set successfully",
|
||||||
status: HttpCode.CREATED,
|
status: HttpCode.CREATED
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
return next(
|
return next(
|
||||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||||
);
|
);
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { fromError } from "zod-validation-error";
|
||||||
import { hash } from "@node-rs/argon2";
|
import { hash } from "@node-rs/argon2";
|
||||||
import { response } from "@server/utils";
|
import { response } from "@server/utils";
|
||||||
import stoi from "@server/utils/stoi";
|
import stoi from "@server/utils/stoi";
|
||||||
|
import logger from "@server/logger";
|
||||||
|
|
||||||
const setResourceAuthMethodsParamsSchema = z.object({
|
const setResourceAuthMethodsParamsSchema = z.object({
|
||||||
resourceId: z.string().transform(Number).pipe(z.number().int().positive()),
|
resourceId: z.string().transform(Number).pipe(z.number().int().positive()),
|
||||||
|
@ -81,6 +82,7 @@ export async function setResourcePincode(
|
||||||
status: HttpCode.CREATED,
|
status: HttpCode.CREATED,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
HttpCode.INTERNAL_SERVER_ERROR,
|
||||||
|
|
|
@ -9,13 +9,20 @@ import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { eq, and, ne } from "drizzle-orm";
|
import { eq, and, ne } from "drizzle-orm";
|
||||||
|
|
||||||
const setResourceRolesBodySchema = z.object({
|
const setResourceRolesBodySchema = z
|
||||||
roleIds: z.array(z.number().int().positive()),
|
.object({
|
||||||
});
|
roleIds: z.array(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
const setResourceRolesParamsSchema = z.object({
|
const setResourceRolesParamsSchema = z
|
||||||
resourceId: z.string().transform(Number).pipe(z.number().int().positive()),
|
.object({
|
||||||
});
|
resourceId: z
|
||||||
|
.string()
|
||||||
|
.transform(Number)
|
||||||
|
.pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export async function setResourceRoles(
|
export async function setResourceRoles(
|
||||||
req: Request,
|
req: Request,
|
||||||
|
@ -99,7 +106,7 @@ export async function setResourceRoles(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Roles set for resource successfully",
|
message: "Roles set for resource successfully",
|
||||||
status: HttpCode.CREATED,
|
status: HttpCode.CREATED
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -9,13 +9,20 @@ import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
|
|
||||||
const setUserResourcesBodySchema = z.object({
|
const setUserResourcesBodySchema = z
|
||||||
userIds: z.array(z.string()),
|
.object({
|
||||||
});
|
userIds: z.array(z.string())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
const setUserResourcesParamsSchema = z.object({
|
const setUserResourcesParamsSchema = z
|
||||||
resourceId: z.string().transform(Number).pipe(z.number().int().positive()),
|
.object({
|
||||||
});
|
resourceId: z
|
||||||
|
.string()
|
||||||
|
.transform(Number)
|
||||||
|
.pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export async function setResourceUsers(
|
export async function setResourceUsers(
|
||||||
req: Request,
|
req: Request,
|
||||||
|
@ -66,7 +73,7 @@ export async function setResourceUsers(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Users set for resource successfully",
|
message: "Users set for resource successfully",
|
||||||
status: HttpCode.CREATED,
|
status: HttpCode.CREATED
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -9,13 +9,20 @@ import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
|
|
||||||
const setResourceWhitelistBodySchema = z.object({
|
const setResourceWhitelistBodySchema = z
|
||||||
|
.object({
|
||||||
emails: z.array(z.string().email()).max(50)
|
emails: z.array(z.string().email()).max(50)
|
||||||
});
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
const setResourceWhitelistParamsSchema = z.object({
|
const setResourceWhitelistParamsSchema = z
|
||||||
resourceId: z.string().transform(Number).pipe(z.number().int().positive())
|
.object({
|
||||||
});
|
resourceId: z
|
||||||
|
.string()
|
||||||
|
.transform(Number)
|
||||||
|
.pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export async function setResourceWhitelist(
|
export async function setResourceWhitelist(
|
||||||
req: Request,
|
req: Request,
|
||||||
|
|
|
@ -10,9 +10,14 @@ import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { subdomainSchema } from "@server/schemas/subdomainSchema";
|
import { subdomainSchema } from "@server/schemas/subdomainSchema";
|
||||||
|
|
||||||
const updateResourceParamsSchema = z.object({
|
const updateResourceParamsSchema = z
|
||||||
resourceId: z.string().transform(Number).pipe(z.number().int().positive()),
|
.object({
|
||||||
});
|
resourceId: z
|
||||||
|
.string()
|
||||||
|
.transform(Number)
|
||||||
|
.pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
const updateResourceBodySchema = z
|
const updateResourceBodySchema = z
|
||||||
.object({
|
.object({
|
||||||
|
@ -21,18 +26,18 @@ const updateResourceBodySchema = z
|
||||||
ssl: z.boolean().optional(),
|
ssl: z.boolean().optional(),
|
||||||
sso: z.boolean().optional(),
|
sso: z.boolean().optional(),
|
||||||
blockAccess: z.boolean().optional(),
|
blockAccess: z.boolean().optional(),
|
||||||
emailWhitelistEnabled: z.boolean().optional(),
|
emailWhitelistEnabled: z.boolean().optional()
|
||||||
// siteId: z.number(),
|
// siteId: z.number(),
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
.refine((data) => Object.keys(data).length > 0, {
|
.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(
|
export async function updateResource(
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
next: NextFunction,
|
next: NextFunction
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const parsedParams = updateResourceParamsSchema.safeParse(req.params);
|
const parsedParams = updateResourceParamsSchema.safeParse(req.params);
|
||||||
|
@ -40,8 +45,8 @@ export async function updateResource(
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
fromError(parsedParams.error).toString(),
|
fromError(parsedParams.error).toString()
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,8 +55,8 @@ export async function updateResource(
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
fromError(parsedBody.error).toString(),
|
fromError(parsedBody.error).toString()
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,8 +73,8 @@ export async function updateResource(
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.NOT_FOUND,
|
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(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
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 = {
|
const updatePayload = {
|
||||||
...updateData,
|
...updateData,
|
||||||
...(fullDomain && { fullDomain }),
|
...(fullDomain && { fullDomain })
|
||||||
};
|
};
|
||||||
|
|
||||||
const updatedResource = await db
|
const updatedResource = await db
|
||||||
|
@ -101,8 +106,8 @@ export async function updateResource(
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.NOT_FOUND,
|
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,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Resource updated successfully",
|
message: "Resource updated successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
|
||||||
"An error occurred",
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,13 +9,17 @@ import logger from "@server/logger";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
|
|
||||||
const addRoleActionParamSchema = z.object({
|
const addRoleActionParamSchema = z
|
||||||
roleId: z.string().transform(Number).pipe(z.number().int().positive()),
|
.object({
|
||||||
});
|
roleId: z.string().transform(Number).pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
const addRoleActionSchema = z.object({
|
const addRoleActionSchema = z
|
||||||
actionId: z.string(),
|
.object({
|
||||||
});
|
actionId: z.string()
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export async function addRoleAction(
|
export async function addRoleAction(
|
||||||
req: Request,
|
req: Request,
|
||||||
|
@ -66,7 +70,7 @@ export async function addRoleAction(
|
||||||
.values({
|
.values({
|
||||||
roleId,
|
roleId,
|
||||||
actionId,
|
actionId,
|
||||||
orgId: role[0].orgId!,
|
orgId: role[0].orgId!
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
@ -75,7 +79,7 @@ export async function addRoleAction(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Action added to role successfully",
|
message: "Action added to role successfully",
|
||||||
status: HttpCode.CREATED,
|
status: HttpCode.CREATED
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -9,13 +9,17 @@ import logger from "@server/logger";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
|
|
||||||
const addRoleSiteParamsSchema = z.object({
|
const addRoleSiteParamsSchema = z
|
||||||
roleId: z.string().transform(Number).pipe(z.number().int().positive()),
|
.object({
|
||||||
});
|
roleId: z.string().transform(Number).pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
const addRoleSiteSchema = z.object({
|
const addRoleSiteSchema = z
|
||||||
siteId: z.string().transform(Number).pipe(z.number().int().positive()),
|
.object({
|
||||||
});
|
siteId: z.string().transform(Number).pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export async function addRoleSite(
|
export async function addRoleSite(
|
||||||
req: Request,
|
req: Request,
|
||||||
|
@ -51,7 +55,7 @@ export async function addRoleSite(
|
||||||
.insert(roleSites)
|
.insert(roleSites)
|
||||||
.values({
|
.values({
|
||||||
roleId,
|
roleId,
|
||||||
siteId,
|
siteId
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
@ -63,7 +67,7 @@ export async function addRoleSite(
|
||||||
for (const resource of siteResources) {
|
for (const resource of siteResources) {
|
||||||
await db.insert(roleResources).values({
|
await db.insert(roleResources).values({
|
||||||
roleId,
|
roleId,
|
||||||
resourceId: resource.resourceId,
|
resourceId: resource.resourceId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,7 +76,7 @@ export async function addRoleSite(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Site added to role successfully",
|
message: "Site added to role successfully",
|
||||||
status: HttpCode.CREATED,
|
status: HttpCode.CREATED
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -10,21 +10,23 @@ import { fromError } from "zod-validation-error";
|
||||||
import { ActionsEnum } from "@server/auth/actions";
|
import { ActionsEnum } from "@server/auth/actions";
|
||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and } from "drizzle-orm";
|
||||||
|
|
||||||
const createRoleParamsSchema = z.object({
|
const createRoleParamsSchema = z
|
||||||
orgId: z.string(),
|
.object({
|
||||||
});
|
orgId: z.string()
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
const createRoleSchema = z
|
const createRoleSchema = z
|
||||||
.object({
|
.object({
|
||||||
name: z.string().min(1).max(255),
|
name: z.string().min(1).max(255),
|
||||||
description: z.string().optional(),
|
description: z.string().optional()
|
||||||
})
|
})
|
||||||
.strict();
|
.strict();
|
||||||
|
|
||||||
export const defaultRoleAllowedActions: ActionsEnum[] = [
|
export const defaultRoleAllowedActions: ActionsEnum[] = [
|
||||||
ActionsEnum.getOrg,
|
ActionsEnum.getOrg,
|
||||||
ActionsEnum.getResource,
|
ActionsEnum.getResource,
|
||||||
ActionsEnum.listResources,
|
ActionsEnum.listResources
|
||||||
];
|
];
|
||||||
|
|
||||||
export type CreateRoleBody = z.infer<typeof createRoleSchema>;
|
export type CreateRoleBody = z.infer<typeof createRoleSchema>;
|
||||||
|
@ -64,7 +66,7 @@ export async function createRole(
|
||||||
const allRoles = await db
|
const allRoles = await db
|
||||||
.select({
|
.select({
|
||||||
roleId: roles.roleId,
|
roleId: roles.roleId,
|
||||||
name: roles.name,
|
name: roles.name
|
||||||
})
|
})
|
||||||
.from(roles)
|
.from(roles)
|
||||||
.leftJoin(orgs, eq(roles.orgId, orgs.orgId))
|
.leftJoin(orgs, eq(roles.orgId, orgs.orgId))
|
||||||
|
@ -84,7 +86,7 @@ export async function createRole(
|
||||||
.insert(roles)
|
.insert(roles)
|
||||||
.values({
|
.values({
|
||||||
...roleData,
|
...roleData,
|
||||||
orgId,
|
orgId
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
@ -94,7 +96,7 @@ export async function createRole(
|
||||||
defaultRoleAllowedActions.map((action) => ({
|
defaultRoleAllowedActions.map((action) => ({
|
||||||
roleId: newRole[0].roleId,
|
roleId: newRole[0].roleId,
|
||||||
actionId: action,
|
actionId: action,
|
||||||
orgId,
|
orgId
|
||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
.execute();
|
.execute();
|
||||||
|
@ -104,7 +106,7 @@ export async function createRole(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Role created successfully",
|
message: "Role created successfully",
|
||||||
status: HttpCode.CREATED,
|
status: HttpCode.CREATED
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -9,13 +9,17 @@ import createHttpError from "http-errors";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
|
|
||||||
const deleteRoleSchema = z.object({
|
const deleteRoleSchema = z
|
||||||
roleId: z.string().transform(Number).pipe(z.number().int().positive()),
|
.object({
|
||||||
});
|
roleId: z.string().transform(Number).pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
const deelteRoleBodySchema = z.object({
|
const deelteRoleBodySchema = z
|
||||||
roleId: z.string().transform(Number).pipe(z.number().int().positive()),
|
.object({
|
||||||
});
|
roleId: z.string().transform(Number).pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export async function deleteRole(
|
export async function deleteRole(
|
||||||
req: Request,
|
req: Request,
|
||||||
|
@ -108,7 +112,7 @@ export async function deleteRole(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Role deleted successfully",
|
message: "Role deleted successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -9,9 +9,11 @@ import createHttpError from "http-errors";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
|
|
||||||
const getRoleSchema = z.object({
|
const getRoleSchema = z
|
||||||
roleId: z.string().transform(Number).pipe(z.number().int().positive()),
|
.object({
|
||||||
});
|
roleId: z.string().transform(Number).pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export async function getRole(
|
export async function getRole(
|
||||||
req: Request,
|
req: Request,
|
||||||
|
@ -51,7 +53,7 @@ export async function getRole(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Role retrieved successfully",
|
message: "Role retrieved successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -9,9 +9,11 @@ import createHttpError from "http-errors";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
|
|
||||||
const listRoleActionsSchema = z.object({
|
const listRoleActionsSchema = z
|
||||||
roleId: z.string().transform(Number).pipe(z.number().int().positive()),
|
.object({
|
||||||
});
|
roleId: z.string().transform(Number).pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export async function listRoleActions(
|
export async function listRoleActions(
|
||||||
req: Request,
|
req: Request,
|
||||||
|
@ -35,7 +37,7 @@ export async function listRoleActions(
|
||||||
.select({
|
.select({
|
||||||
actionId: actions.actionId,
|
actionId: actions.actionId,
|
||||||
name: actions.name,
|
name: actions.name,
|
||||||
description: actions.description,
|
description: actions.description
|
||||||
})
|
})
|
||||||
.from(roleActions)
|
.from(roleActions)
|
||||||
.innerJoin(actions, eq(roleActions.actionId, actions.actionId))
|
.innerJoin(actions, eq(roleActions.actionId, actions.actionId))
|
||||||
|
@ -48,7 +50,7 @@ export async function listRoleActions(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Role actions retrieved successfully",
|
message: "Role actions retrieved successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -9,9 +9,11 @@ import createHttpError from "http-errors";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
|
|
||||||
const listRoleResourcesSchema = z.object({
|
const listRoleResourcesSchema = z
|
||||||
roleId: z.string().transform(Number).pipe(z.number().int().positive()),
|
.object({
|
||||||
});
|
roleId: z.string().transform(Number).pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export async function listRoleResources(
|
export async function listRoleResources(
|
||||||
req: Request,
|
req: Request,
|
||||||
|
@ -35,7 +37,7 @@ export async function listRoleResources(
|
||||||
.select({
|
.select({
|
||||||
resourceId: resources.resourceId,
|
resourceId: resources.resourceId,
|
||||||
name: resources.name,
|
name: resources.name,
|
||||||
subdomain: resources.subdomain,
|
subdomain: resources.subdomain
|
||||||
})
|
})
|
||||||
.from(roleResources)
|
.from(roleResources)
|
||||||
.innerJoin(
|
.innerJoin(
|
||||||
|
@ -51,7 +53,7 @@ export async function listRoleResources(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Role resources retrieved successfully",
|
message: "Role resources retrieved successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -9,9 +9,11 @@ import createHttpError from "http-errors";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
|
|
||||||
const listRoleSitesSchema = z.object({
|
const listRoleSitesSchema = z
|
||||||
roleId: z.string().transform(Number).pipe(z.number().int().positive()),
|
.object({
|
||||||
});
|
roleId: z.string().transform(Number).pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export async function listRoleSites(
|
export async function listRoleSites(
|
||||||
req: Request,
|
req: Request,
|
||||||
|
@ -34,7 +36,7 @@ export async function listRoleSites(
|
||||||
const roleSitesList = await db
|
const roleSitesList = await db
|
||||||
.select({
|
.select({
|
||||||
siteId: sites.siteId,
|
siteId: sites.siteId,
|
||||||
name: sites.name,
|
name: sites.name
|
||||||
})
|
})
|
||||||
.from(roleSites)
|
.from(roleSites)
|
||||||
.innerJoin(sites, eq(roleSites.siteId, sites.siteId))
|
.innerJoin(sites, eq(roleSites.siteId, sites.siteId))
|
||||||
|
@ -47,7 +49,7 @@ export async function listRoleSites(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Role sites retrieved successfully",
|
message: "Role sites retrieved successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -10,9 +10,11 @@ import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import stoi from "@server/utils/stoi";
|
import stoi from "@server/utils/stoi";
|
||||||
|
|
||||||
const listRolesParamsSchema = z.object({
|
const listRolesParamsSchema = z
|
||||||
orgId: z.string(),
|
.object({
|
||||||
});
|
orgId: z.string()
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
const listRolesSchema = z.object({
|
const listRolesSchema = z.object({
|
||||||
limit: z
|
limit: z
|
||||||
|
@ -26,7 +28,7 @@ const listRolesSchema = z.object({
|
||||||
.optional()
|
.optional()
|
||||||
.default("0")
|
.default("0")
|
||||||
.transform(Number)
|
.transform(Number)
|
||||||
.pipe(z.number().int().nonnegative()),
|
.pipe(z.number().int().nonnegative())
|
||||||
});
|
});
|
||||||
|
|
||||||
async function queryRoles(orgId: string, limit: number, offset: number) {
|
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,
|
isAdmin: roles.isAdmin,
|
||||||
name: roles.name,
|
name: roles.name,
|
||||||
description: roles.description,
|
description: roles.description,
|
||||||
orgName: orgs.name,
|
orgName: orgs.name
|
||||||
})
|
})
|
||||||
.from(roles)
|
.from(roles)
|
||||||
.leftJoin(orgs, eq(roles.orgId, orgs.orgId))
|
.leftJoin(orgs, eq(roles.orgId, orgs.orgId))
|
||||||
|
@ -100,13 +102,13 @@ export async function listRoles(
|
||||||
pagination: {
|
pagination: {
|
||||||
total: totalCount,
|
total: totalCount,
|
||||||
limit,
|
limit,
|
||||||
offset,
|
offset
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Roles retrieved successfully",
|
message: "Roles retrieved successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -9,13 +9,17 @@ import createHttpError from "http-errors";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
|
|
||||||
const removeRoleActionParamsSchema = z.object({
|
const removeRoleActionParamsSchema = z
|
||||||
roleId: z.string().transform(Number).pipe(z.number().int().positive()),
|
.object({
|
||||||
});
|
roleId: z.string().transform(Number).pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
const removeRoleActionSchema = z.object({
|
const removeRoleActionSchema = z
|
||||||
actionId: z.string(),
|
.object({
|
||||||
});
|
actionId: z.string()
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export async function removeRoleAction(
|
export async function removeRoleAction(
|
||||||
req: Request,
|
req: Request,
|
||||||
|
@ -71,7 +75,7 @@ export async function removeRoleAction(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Action removed from role successfully",
|
message: "Action removed from role successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -9,13 +9,20 @@ import createHttpError from "http-errors";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
|
|
||||||
const removeRoleResourceParamsSchema = z.object({
|
const removeRoleResourceParamsSchema = z
|
||||||
roleId: z.string().transform(Number).pipe(z.number().int().positive()),
|
.object({
|
||||||
});
|
roleId: z.string().transform(Number).pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
const removeRoleResourceSchema = z.object({
|
const removeRoleResourceSchema = z
|
||||||
resourceId: z.string().transform(Number).pipe(z.number().int().positive()),
|
.object({
|
||||||
});
|
resourceId: z
|
||||||
|
.string()
|
||||||
|
.transform(Number)
|
||||||
|
.pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export async function removeRoleResource(
|
export async function removeRoleResource(
|
||||||
req: Request,
|
req: Request,
|
||||||
|
@ -71,7 +78,7 @@ export async function removeRoleResource(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Resource removed from role successfully",
|
message: "Resource removed from role successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -9,13 +9,17 @@ import createHttpError from "http-errors";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
|
|
||||||
const removeRoleSiteParamsSchema = z.object({
|
const removeRoleSiteParamsSchema = z
|
||||||
roleId: z.string().transform(Number).pipe(z.number().int().positive()),
|
.object({
|
||||||
});
|
roleId: z.string().transform(Number).pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
const removeRoleSiteSchema = z.object({
|
const removeRoleSiteSchema = z
|
||||||
siteId: z.string().transform(Number).pipe(z.number().int().positive()),
|
.object({
|
||||||
});
|
siteId: z.string().transform(Number).pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export async function removeRoleSite(
|
export async function removeRoleSite(
|
||||||
req: Request,
|
req: Request,
|
||||||
|
@ -85,7 +89,7 @@ export async function removeRoleSite(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Site removed from role successfully",
|
message: "Site removed from role successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -9,18 +9,20 @@ import createHttpError from "http-errors";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
|
|
||||||
const updateRoleParamsSchema = z.object({
|
const updateRoleParamsSchema = z
|
||||||
roleId: z.string().transform(Number).pipe(z.number().int().positive()),
|
.object({
|
||||||
});
|
roleId: z.string().transform(Number).pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
const updateRoleBodySchema = z
|
const updateRoleBodySchema = z
|
||||||
.object({
|
.object({
|
||||||
name: z.string().min(1).max(255).optional(),
|
name: z.string().min(1).max(255).optional(),
|
||||||
description: z.string().optional(),
|
description: z.string().optional()
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
.refine((data) => Object.keys(data).length > 0, {
|
.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(
|
export async function updateRole(
|
||||||
|
@ -96,7 +98,7 @@ export async function updateRole(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Role updated successfully",
|
message: "Role updated successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
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 response from "@server/utils/response";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
|
@ -14,9 +14,11 @@ import { hash } from "@node-rs/argon2";
|
||||||
import { newts } from "@server/db/schema";
|
import { newts } from "@server/db/schema";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
|
|
||||||
const createSiteParamsSchema = z.object({
|
const createSiteParamsSchema = z
|
||||||
orgId: z.string(),
|
.object({
|
||||||
});
|
orgId: z.string()
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
const createSiteSchema = z
|
const createSiteSchema = z
|
||||||
.object({
|
.object({
|
||||||
|
@ -27,18 +29,13 @@ const createSiteSchema = z
|
||||||
subnet: z.string(),
|
subnet: z.string(),
|
||||||
newtId: z.string().optional(),
|
newtId: z.string().optional(),
|
||||||
secret: z.string().optional(),
|
secret: z.string().optional(),
|
||||||
type: z.string(),
|
type: z.string()
|
||||||
})
|
})
|
||||||
.strict();
|
.strict();
|
||||||
|
|
||||||
export type CreateSiteBody = z.infer<typeof createSiteSchema>;
|
export type CreateSiteBody = z.infer<typeof createSiteSchema>;
|
||||||
|
|
||||||
export type CreateSiteResponse = {
|
export type CreateSiteResponse = Site;
|
||||||
name: string;
|
|
||||||
siteId: number;
|
|
||||||
orgId: string;
|
|
||||||
niceId: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function createSite(
|
export async function createSite(
|
||||||
req: Request,
|
req: Request,
|
||||||
|
@ -85,14 +82,14 @@ export async function createSite(
|
||||||
name,
|
name,
|
||||||
niceId,
|
niceId,
|
||||||
subnet,
|
subnet,
|
||||||
type,
|
type
|
||||||
};
|
};
|
||||||
|
|
||||||
if (pubKey && type == "wireguard") {
|
if (pubKey && type == "wireguard") {
|
||||||
// we dont add the pubKey for newts because the newt will generate it
|
// we dont add the pubKey for newts because the newt will generate it
|
||||||
payload = {
|
payload = {
|
||||||
...payload,
|
...payload,
|
||||||
pubKey,
|
pubKey
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,14 +109,14 @@ export async function createSite(
|
||||||
|
|
||||||
await db.insert(roleSites).values({
|
await db.insert(roleSites).values({
|
||||||
roleId: adminRole[0].roleId,
|
roleId: adminRole[0].roleId,
|
||||||
siteId: newSite.siteId,
|
siteId: newSite.siteId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (req.userOrgRoleId != adminRole[0].roleId) {
|
if (req.userOrgRoleId != adminRole[0].roleId) {
|
||||||
// make sure the user can access the site
|
// make sure the user can access the site
|
||||||
db.insert(userSites).values({
|
db.insert(userSites).values({
|
||||||
userId: req.user?.userId!,
|
userId: req.user?.userId!,
|
||||||
siteId: newSite.siteId,
|
siteId: newSite.siteId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,14 +126,14 @@ export async function createSite(
|
||||||
memoryCost: 19456,
|
memoryCost: 19456,
|
||||||
timeCost: 2,
|
timeCost: 2,
|
||||||
outputLen: 32,
|
outputLen: 32,
|
||||||
parallelism: 1,
|
parallelism: 1
|
||||||
});
|
});
|
||||||
|
|
||||||
await db.insert(newts).values({
|
await db.insert(newts).values({
|
||||||
newtId: newtId!,
|
newtId: newtId!,
|
||||||
secretHash,
|
secretHash,
|
||||||
siteId: newSite.siteId,
|
siteId: newSite.siteId,
|
||||||
dateCreated: moment().toISOString(),
|
dateCreated: moment().toISOString()
|
||||||
});
|
});
|
||||||
} else if (type == "wireguard") {
|
} else if (type == "wireguard") {
|
||||||
if (!pubKey) {
|
if (!pubKey) {
|
||||||
|
@ -149,23 +146,19 @@ export async function createSite(
|
||||||
}
|
}
|
||||||
await addPeer(exitNodeId, {
|
await addPeer(exitNodeId, {
|
||||||
publicKey: pubKey,
|
publicKey: pubKey,
|
||||||
allowedIps: [],
|
allowedIps: []
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return response(res, {
|
return response<CreateSiteResponse>(res, {
|
||||||
data: {
|
data: newSite,
|
||||||
name: newSite.name,
|
|
||||||
niceId: newSite.niceId,
|
|
||||||
siteId: newSite.siteId,
|
|
||||||
orgId: newSite.orgId,
|
|
||||||
},
|
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Site created successfully",
|
message: "Site created successfully",
|
||||||
status: HttpCode.CREATED,
|
status: HttpCode.CREATED
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
return next(
|
return next(
|
||||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||||
);
|
);
|
||||||
|
|
|
@ -11,9 +11,11 @@ import { deletePeer } from "../gerbil/peers";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { sendToClient } from "../ws";
|
import { sendToClient } from "../ws";
|
||||||
|
|
||||||
const deleteSiteSchema = z.object({
|
const deleteSiteSchema = z
|
||||||
siteId: z.string().transform(Number).pipe(z.number().int().positive()),
|
.object({
|
||||||
});
|
siteId: z.string().transform(Number).pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export async function deleteSite(
|
export async function deleteSite(
|
||||||
req: Request,
|
req: Request,
|
||||||
|
@ -60,7 +62,7 @@ export async function deleteSite(
|
||||||
if (deletedNewt) {
|
if (deletedNewt) {
|
||||||
const payload = {
|
const payload = {
|
||||||
type: `newt/terminate`,
|
type: `newt/terminate`,
|
||||||
data: {},
|
data: {}
|
||||||
};
|
};
|
||||||
sendToClient(deletedNewt.newtId, payload);
|
sendToClient(deletedNewt.newtId, payload);
|
||||||
|
|
||||||
|
@ -79,7 +81,7 @@ export async function deleteSite(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Site deleted successfully",
|
message: "Site deleted successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -10,7 +10,8 @@ import logger from "@server/logger";
|
||||||
import stoi from "@server/utils/stoi";
|
import stoi from "@server/utils/stoi";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
|
|
||||||
const getSiteSchema = z.object({
|
const getSiteSchema = z
|
||||||
|
.object({
|
||||||
siteId: z
|
siteId: z
|
||||||
.string()
|
.string()
|
||||||
.optional()
|
.optional()
|
||||||
|
@ -18,8 +19,9 @@ const getSiteSchema = z.object({
|
||||||
.pipe(z.number().int().positive().optional())
|
.pipe(z.number().int().positive().optional())
|
||||||
.optional(),
|
.optional(),
|
||||||
niceId: z.string().optional(),
|
niceId: z.string().optional(),
|
||||||
orgId: z.string().optional(),
|
orgId: z.string().optional()
|
||||||
});
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export type GetSiteResponse = {
|
export type GetSiteResponse = {
|
||||||
siteId: number;
|
siteId: number;
|
||||||
|
@ -79,15 +81,15 @@ export async function getSite(
|
||||||
siteId: site[0].siteId,
|
siteId: site[0].siteId,
|
||||||
niceId: site[0].niceId,
|
niceId: site[0].niceId,
|
||||||
name: site[0].name,
|
name: site[0].name,
|
||||||
subnet: site[0].subnet,
|
subnet: site[0].subnet
|
||||||
},
|
},
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Site retrieved successfully",
|
message: "Site retrieved successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Error from getSite: ", error);
|
logger.error(error);
|
||||||
return next(
|
return next(
|
||||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||||
);
|
);
|
||||||
|
|
|
@ -9,9 +9,11 @@ import createHttpError from "http-errors";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
|
|
||||||
const listSiteRolesSchema = z.object({
|
const listSiteRolesSchema = z
|
||||||
siteId: z.string().transform(Number).pipe(z.number().int().positive()),
|
.object({
|
||||||
});
|
siteId: z.string().transform(Number).pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export async function listSiteRoles(
|
export async function listSiteRoles(
|
||||||
req: Request,
|
req: Request,
|
||||||
|
@ -36,7 +38,7 @@ export async function listSiteRoles(
|
||||||
roleId: roles.roleId,
|
roleId: roles.roleId,
|
||||||
name: roles.name,
|
name: roles.name,
|
||||||
description: roles.description,
|
description: roles.description,
|
||||||
isAdmin: roles.isAdmin,
|
isAdmin: roles.isAdmin
|
||||||
})
|
})
|
||||||
.from(roleSites)
|
.from(roleSites)
|
||||||
.innerJoin(roles, eq(roleSites.roleId, roles.roleId))
|
.innerJoin(roles, eq(roleSites.roleId, roles.roleId))
|
||||||
|
@ -47,7 +49,7 @@ export async function listSiteRoles(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Site roles retrieved successfully",
|
message: "Site roles retrieved successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
import { orgs, roleSites, sites, userSites } from "@server/db/schema";
|
import { orgs, roleSites, sites, userSites } from "@server/db/schema";
|
||||||
|
import logger from "@server/logger";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import response from "@server/utils/response";
|
import response from "@server/utils/response";
|
||||||
import { and, count, eq, inArray, or, sql } from "drizzle-orm";
|
import { and, count, eq, inArray, or, sql } from "drizzle-orm";
|
||||||
|
@ -8,9 +9,11 @@ import createHttpError from "http-errors";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
|
|
||||||
const listSitesParamsSchema = z.object({
|
const listSitesParamsSchema = z
|
||||||
orgId: z.string(),
|
.object({
|
||||||
});
|
orgId: z.string()
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
const listSitesSchema = z.object({
|
const listSitesSchema = z.object({
|
||||||
limit: z
|
limit: z
|
||||||
|
@ -24,7 +27,7 @@ const listSitesSchema = z.object({
|
||||||
.optional()
|
.optional()
|
||||||
.default("0")
|
.default("0")
|
||||||
.transform(Number)
|
.transform(Number)
|
||||||
.pipe(z.number().int().nonnegative()),
|
.pipe(z.number().int().nonnegative())
|
||||||
});
|
});
|
||||||
|
|
||||||
function querySites(orgId: string, accessibleSiteIds: number[]) {
|
function querySites(orgId: string, accessibleSiteIds: number[]) {
|
||||||
|
@ -39,15 +42,15 @@ function querySites(orgId: string, accessibleSiteIds: number[]) {
|
||||||
megabytesOut: sites.megabytesOut,
|
megabytesOut: sites.megabytesOut,
|
||||||
orgName: orgs.name,
|
orgName: orgs.name,
|
||||||
type: sites.type,
|
type: sites.type,
|
||||||
online: sites.online,
|
online: sites.online
|
||||||
})
|
})
|
||||||
.from(sites)
|
.from(sites)
|
||||||
.leftJoin(orgs, eq(sites.orgId, orgs.orgId))
|
.leftJoin(orgs, eq(sites.orgId, orgs.orgId))
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
inArray(sites.siteId, accessibleSiteIds),
|
inArray(sites.siteId, accessibleSiteIds),
|
||||||
eq(sites.orgId, orgId),
|
eq(sites.orgId, orgId)
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +62,7 @@ export type ListSitesResponse = {
|
||||||
export async function listSites(
|
export async function listSites(
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
next: NextFunction,
|
next: NextFunction
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const parsedQuery = listSitesSchema.safeParse(req.query);
|
const parsedQuery = listSitesSchema.safeParse(req.query);
|
||||||
|
@ -67,8 +70,8 @@ export async function listSites(
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
fromError(parsedQuery.error),
|
fromError(parsedQuery.error)
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const { limit, offset } = parsedQuery.data;
|
const { limit, offset } = parsedQuery.data;
|
||||||
|
@ -78,8 +81,8 @@ export async function listSites(
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
fromError(parsedParams.error),
|
fromError(parsedParams.error)
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const { orgId } = parsedParams.data;
|
const { orgId } = parsedParams.data;
|
||||||
|
@ -88,22 +91,22 @@ export async function listSites(
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.FORBIDDEN,
|
HttpCode.FORBIDDEN,
|
||||||
"User does not have access to this organization",
|
"User does not have access to this organization"
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const accessibleSites = await db
|
const accessibleSites = await db
|
||||||
.select({
|
.select({
|
||||||
siteId: sql<number>`COALESCE(${userSites.siteId}, ${roleSites.siteId})`,
|
siteId: sql<number>`COALESCE(${userSites.siteId}, ${roleSites.siteId})`
|
||||||
})
|
})
|
||||||
.from(userSites)
|
.from(userSites)
|
||||||
.fullJoin(roleSites, eq(userSites.siteId, roleSites.siteId))
|
.fullJoin(roleSites, eq(userSites.siteId, roleSites.siteId))
|
||||||
.where(
|
.where(
|
||||||
or(
|
or(
|
||||||
eq(userSites.userId, req.user!.userId),
|
eq(userSites.userId, req.user!.userId),
|
||||||
eq(roleSites.roleId, req.userOrgRoleId!),
|
eq(roleSites.roleId, req.userOrgRoleId!)
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const accessibleSiteIds = accessibleSites.map((site) => site.siteId);
|
const accessibleSiteIds = accessibleSites.map((site) => site.siteId);
|
||||||
|
@ -115,8 +118,8 @@ export async function listSites(
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
inArray(sites.siteId, accessibleSiteIds),
|
inArray(sites.siteId, accessibleSiteIds),
|
||||||
eq(sites.orgId, orgId),
|
eq(sites.orgId, orgId)
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const sitesList = await baseQuery.limit(limit).offset(offset);
|
const sitesList = await baseQuery.limit(limit).offset(offset);
|
||||||
|
@ -129,20 +132,18 @@ export async function listSites(
|
||||||
pagination: {
|
pagination: {
|
||||||
total: totalCount,
|
total: totalCount,
|
||||||
limit,
|
limit,
|
||||||
offset,
|
offset
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Sites retrieved successfully",
|
message: "Sites retrieved successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
|
||||||
"An error occurred",
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,14 +9,16 @@ import createHttpError from "http-errors";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
|
|
||||||
const updateSiteParamsSchema = z.object({
|
const updateSiteParamsSchema = z
|
||||||
siteId: z.string().transform(Number).pipe(z.number().int().positive()),
|
.object({
|
||||||
});
|
siteId: z.string().transform(Number).pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
const updateSiteBodySchema = z
|
const updateSiteBodySchema = z
|
||||||
.object({
|
.object({
|
||||||
name: z.string().min(1).max(255).optional(),
|
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(),
|
// pubKey: z.string().optional(),
|
||||||
// subnet: z.string().optional(),
|
// subnet: z.string().optional(),
|
||||||
// exitNode: z.number().int().positive().optional(),
|
// exitNode: z.number().int().positive().optional(),
|
||||||
|
@ -25,7 +27,7 @@ const updateSiteBodySchema = z
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
.refine((data) => Object.keys(data).length > 0, {
|
.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(
|
export async function updateSite(
|
||||||
|
@ -77,7 +79,7 @@ export async function updateSite(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Site updated successfully",
|
message: "Site updated successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -12,9 +12,14 @@ import { isIpInCidr } from "@server/utils/ip";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { addTargets } from "../newt/targets";
|
import { addTargets } from "../newt/targets";
|
||||||
|
|
||||||
const createTargetParamsSchema = z.object({
|
const createTargetParamsSchema = z
|
||||||
resourceId: z.string().transform(Number).pipe(z.number().int().positive()),
|
.object({
|
||||||
});
|
resourceId: z
|
||||||
|
.string()
|
||||||
|
.transform(Number)
|
||||||
|
.pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
const createTargetSchema = z
|
const createTargetSchema = z
|
||||||
.object({
|
.object({
|
||||||
|
@ -22,7 +27,7 @@ const createTargetSchema = z
|
||||||
method: z.string().min(1).max(10),
|
method: z.string().min(1).max(10),
|
||||||
port: z.number().int().min(1).max(65535),
|
port: z.number().int().min(1).max(65535),
|
||||||
protocol: z.string().optional(),
|
protocol: z.string().optional(),
|
||||||
enabled: z.boolean().default(true),
|
enabled: z.boolean().default(true)
|
||||||
})
|
})
|
||||||
.strict();
|
.strict();
|
||||||
|
|
||||||
|
@ -61,7 +66,7 @@ export async function createTarget(
|
||||||
// get the resource
|
// get the resource
|
||||||
const [resource] = await db
|
const [resource] = await db
|
||||||
.select({
|
.select({
|
||||||
siteId: resources.siteId,
|
siteId: resources.siteId
|
||||||
})
|
})
|
||||||
.from(resources)
|
.from(resources)
|
||||||
.where(eq(resources.resourceId, resourceId));
|
.where(eq(resources.resourceId, resourceId));
|
||||||
|
@ -91,7 +96,10 @@ export async function createTarget(
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure the target is within the site subnet
|
// 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(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
|
@ -102,7 +110,7 @@ export async function createTarget(
|
||||||
|
|
||||||
// Fetch resources for this site
|
// Fetch resources for this site
|
||||||
const resourcesRes = await db.query.resources.findMany({
|
const resourcesRes = await db.query.resources.findMany({
|
||||||
where: eq(resources.siteId, site.siteId),
|
where: eq(resources.siteId, site.siteId)
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: is this all inefficient?
|
// TODO: is this all inefficient?
|
||||||
|
@ -112,7 +120,7 @@ export async function createTarget(
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
resourcesRes.map(async (resource) => {
|
resourcesRes.map(async (resource) => {
|
||||||
const targetsRes = await db.query.targets.findMany({
|
const targetsRes = await db.query.targets.findMany({
|
||||||
where: eq(targets.resourceId, resource.resourceId),
|
where: eq(targets.resourceId, resource.resourceId)
|
||||||
});
|
});
|
||||||
targetsRes.forEach((target) => {
|
targetsRes.forEach((target) => {
|
||||||
targetIps.push(`${target.ip}/32`);
|
targetIps.push(`${target.ip}/32`);
|
||||||
|
@ -147,7 +155,7 @@ export async function createTarget(
|
||||||
resourceId,
|
resourceId,
|
||||||
protocol: "tcp", // hard code for now
|
protocol: "tcp", // hard code for now
|
||||||
internalPort,
|
internalPort,
|
||||||
...targetData,
|
...targetData
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
@ -155,7 +163,7 @@ export async function createTarget(
|
||||||
if (site.type == "wireguard") {
|
if (site.type == "wireguard") {
|
||||||
await addPeer(site.exitNodeId!, {
|
await addPeer(site.exitNodeId!, {
|
||||||
publicKey: site.pubKey,
|
publicKey: site.pubKey,
|
||||||
allowedIps: targetIps.flat(),
|
allowedIps: targetIps.flat()
|
||||||
});
|
});
|
||||||
} else if (site.type == "newt") {
|
} else if (site.type == "newt") {
|
||||||
// get the newt on the site by querying the newt table for siteId
|
// get the newt on the site by querying the newt table for siteId
|
||||||
|
@ -174,7 +182,7 @@ export async function createTarget(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Target created successfully",
|
message: "Target created successfully",
|
||||||
status: HttpCode.CREATED,
|
status: HttpCode.CREATED
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -11,9 +11,11 @@ import { addPeer } from "../gerbil/peers";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { removeTargets } from "../newt/targets";
|
import { removeTargets } from "../newt/targets";
|
||||||
|
|
||||||
const deleteTargetSchema = z.object({
|
const deleteTargetSchema = z
|
||||||
targetId: z.string().transform(Number).pipe(z.number().int().positive()),
|
.object({
|
||||||
});
|
targetId: z.string().transform(Number).pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export async function deleteTarget(
|
export async function deleteTarget(
|
||||||
req: Request,
|
req: Request,
|
||||||
|
@ -49,7 +51,7 @@ export async function deleteTarget(
|
||||||
// get the resource
|
// get the resource
|
||||||
const [resource] = await db
|
const [resource] = await db
|
||||||
.select({
|
.select({
|
||||||
siteId: resources.siteId,
|
siteId: resources.siteId
|
||||||
})
|
})
|
||||||
.from(resources)
|
.from(resources)
|
||||||
.where(eq(resources.resourceId, deletedTarget.resourceId!));
|
.where(eq(resources.resourceId, deletedTarget.resourceId!));
|
||||||
|
@ -83,14 +85,14 @@ export async function deleteTarget(
|
||||||
// TODO: is this all inefficient?
|
// TODO: is this all inefficient?
|
||||||
// Fetch resources for this site
|
// Fetch resources for this site
|
||||||
const resourcesRes = await db.query.resources.findMany({
|
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
|
// Fetch targets for all resources of this site
|
||||||
const targetIps = await Promise.all(
|
const targetIps = await Promise.all(
|
||||||
resourcesRes.map(async (resource) => {
|
resourcesRes.map(async (resource) => {
|
||||||
const targetsRes = await db.query.targets.findMany({
|
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`);
|
return targetsRes.map((target) => `${target.ip}/32`);
|
||||||
})
|
})
|
||||||
|
@ -98,7 +100,7 @@ export async function deleteTarget(
|
||||||
|
|
||||||
await addPeer(site.exitNodeId!, {
|
await addPeer(site.exitNodeId!, {
|
||||||
publicKey: site.pubKey,
|
publicKey: site.pubKey,
|
||||||
allowedIps: targetIps.flat(),
|
allowedIps: targetIps.flat()
|
||||||
});
|
});
|
||||||
} else if (site.type == "newt") {
|
} else if (site.type == "newt") {
|
||||||
// get the newt on the site by querying the newt table for siteId
|
// get the newt on the site by querying the newt table for siteId
|
||||||
|
@ -117,7 +119,7 @@ export async function deleteTarget(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Target deleted successfully",
|
message: "Target deleted successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -9,9 +9,11 @@ import createHttpError from "http-errors";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
|
|
||||||
const getTargetSchema = z.object({
|
const getTargetSchema = z
|
||||||
targetId: z.string().transform(Number).pipe(z.number().int().positive()),
|
.object({
|
||||||
});
|
targetId: z.string().transform(Number).pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export async function getTarget(
|
export async function getTarget(
|
||||||
req: Request,
|
req: Request,
|
||||||
|
@ -51,7 +53,7 @@ export async function getTarget(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Target retrieved successfully",
|
message: "Target retrieved successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -9,9 +9,14 @@ import { z } from "zod";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
|
|
||||||
const listTargetsParamsSchema = z.object({
|
const listTargetsParamsSchema = z
|
||||||
resourceId: z.string().transform(Number).pipe(z.number().int().positive()),
|
.object({
|
||||||
});
|
resourceId: z
|
||||||
|
.string()
|
||||||
|
.transform(Number)
|
||||||
|
.pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
const listTargetsSchema = z.object({
|
const listTargetsSchema = z.object({
|
||||||
limit: z
|
limit: z
|
||||||
|
@ -25,7 +30,7 @@ const listTargetsSchema = z.object({
|
||||||
.optional()
|
.optional()
|
||||||
.default("0")
|
.default("0")
|
||||||
.transform(Number)
|
.transform(Number)
|
||||||
.pipe(z.number().int().nonnegative()),
|
.pipe(z.number().int().nonnegative())
|
||||||
});
|
});
|
||||||
|
|
||||||
function queryTargets(resourceId: number) {
|
function queryTargets(resourceId: number) {
|
||||||
|
@ -37,7 +42,7 @@ function queryTargets(resourceId: number) {
|
||||||
port: targets.port,
|
port: targets.port,
|
||||||
protocol: targets.protocol,
|
protocol: targets.protocol,
|
||||||
enabled: targets.enabled,
|
enabled: targets.enabled,
|
||||||
resourceId: targets.resourceId,
|
resourceId: targets.resourceId
|
||||||
// resourceName: resources.name,
|
// resourceName: resources.name,
|
||||||
})
|
})
|
||||||
.from(targets)
|
.from(targets)
|
||||||
|
@ -97,13 +102,13 @@ export async function listTargets(
|
||||||
pagination: {
|
pagination: {
|
||||||
total: totalCount,
|
total: totalCount,
|
||||||
limit,
|
limit,
|
||||||
offset,
|
offset
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Targets retrieved successfully",
|
message: "Targets retrieved successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -11,20 +11,22 @@ import { fromError } from "zod-validation-error";
|
||||||
import { addPeer } from "../gerbil/peers";
|
import { addPeer } from "../gerbil/peers";
|
||||||
import { addTargets } from "../newt/targets";
|
import { addTargets } from "../newt/targets";
|
||||||
|
|
||||||
const updateTargetParamsSchema = z.object({
|
const updateTargetParamsSchema = z
|
||||||
targetId: z.string().transform(Number).pipe(z.number().int().positive()),
|
.object({
|
||||||
});
|
targetId: z.string().transform(Number).pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
const updateTargetBodySchema = z
|
const updateTargetBodySchema = z
|
||||||
.object({
|
.object({
|
||||||
ip: z.string().ip().optional(), // for now we cant update the ip; you will have to delete
|
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(),
|
method: z.string().min(1).max(10).optional(),
|
||||||
port: z.number().int().min(1).max(65535).optional(),
|
port: z.number().int().min(1).max(65535).optional(),
|
||||||
enabled: z.boolean().optional(),
|
enabled: z.boolean().optional()
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
.refine((data) => Object.keys(data).length > 0, {
|
.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(
|
export async function updateTarget(
|
||||||
|
@ -74,7 +76,7 @@ export async function updateTarget(
|
||||||
// get the resource
|
// get the resource
|
||||||
const [resource] = await db
|
const [resource] = await db
|
||||||
.select({
|
.select({
|
||||||
siteId: resources.siteId,
|
siteId: resources.siteId
|
||||||
})
|
})
|
||||||
.from(resources)
|
.from(resources)
|
||||||
.where(eq(resources.resourceId, updatedTarget.resourceId!));
|
.where(eq(resources.resourceId, updatedTarget.resourceId!));
|
||||||
|
@ -107,14 +109,14 @@ export async function updateTarget(
|
||||||
// TODO: is this all inefficient?
|
// TODO: is this all inefficient?
|
||||||
// Fetch resources for this site
|
// Fetch resources for this site
|
||||||
const resourcesRes = await db.query.resources.findMany({
|
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
|
// Fetch targets for all resources of this site
|
||||||
const targetIps = await Promise.all(
|
const targetIps = await Promise.all(
|
||||||
resourcesRes.map(async (resource) => {
|
resourcesRes.map(async (resource) => {
|
||||||
const targetsRes = await db.query.targets.findMany({
|
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`);
|
return targetsRes.map((target) => `${target.ip}/32`);
|
||||||
})
|
})
|
||||||
|
@ -122,7 +124,7 @@ export async function updateTarget(
|
||||||
|
|
||||||
await addPeer(site.exitNodeId!, {
|
await addPeer(site.exitNodeId!, {
|
||||||
publicKey: site.pubKey,
|
publicKey: site.pubKey,
|
||||||
allowedIps: targetIps.flat(),
|
allowedIps: targetIps.flat()
|
||||||
});
|
});
|
||||||
} else if (site.type == "newt") {
|
} else if (site.type == "newt") {
|
||||||
// get the newt on the site by querying the newt table for siteId
|
// get the newt on the site by querying the newt table for siteId
|
||||||
|
@ -140,7 +142,7 @@ export async function updateTarget(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Target updated successfully",
|
message: "Target updated successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -11,10 +11,12 @@ import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { isWithinExpirationDate } from "oslo";
|
import { isWithinExpirationDate } from "oslo";
|
||||||
|
|
||||||
const acceptInviteBodySchema = z.object({
|
const acceptInviteBodySchema = z
|
||||||
|
.object({
|
||||||
token: z.string(),
|
token: z.string(),
|
||||||
inviteId: z.string(),
|
inviteId: z.string()
|
||||||
});
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export type AcceptInviteResponse = {
|
export type AcceptInviteResponse = {
|
||||||
accepted: boolean;
|
accepted: boolean;
|
||||||
|
@ -64,7 +66,7 @@ export async function acceptInvite(
|
||||||
memoryCost: 19456,
|
memoryCost: 19456,
|
||||||
timeCost: 2,
|
timeCost: 2,
|
||||||
outputLen: 32,
|
outputLen: 32,
|
||||||
parallelism: 1,
|
parallelism: 1
|
||||||
});
|
});
|
||||||
if (!validToken) {
|
if (!validToken) {
|
||||||
return next(
|
return next(
|
||||||
|
@ -121,7 +123,7 @@ export async function acceptInvite(
|
||||||
await db.insert(userOrgs).values({
|
await db.insert(userOrgs).values({
|
||||||
userId: existingUser[0].userId,
|
userId: existingUser[0].userId,
|
||||||
orgId: existingInvite[0].orgId,
|
orgId: existingInvite[0].orgId,
|
||||||
roleId: existingInvite[0].roleId,
|
roleId: existingInvite[0].roleId
|
||||||
});
|
});
|
||||||
|
|
||||||
// delete the invite
|
// delete the invite
|
||||||
|
@ -132,7 +134,7 @@ export async function acceptInvite(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Invite accepted",
|
message: "Invite accepted",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -9,11 +9,13 @@ import logger from "@server/logger";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
|
|
||||||
const addUserActionSchema = z.object({
|
const addUserActionSchema = z
|
||||||
|
.object({
|
||||||
userId: z.string(),
|
userId: z.string(),
|
||||||
actionId: z.string(),
|
actionId: z.string(),
|
||||||
orgId: z.string(),
|
orgId: z.string()
|
||||||
});
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export async function addUserAction(
|
export async function addUserAction(
|
||||||
req: Request,
|
req: Request,
|
||||||
|
@ -52,7 +54,7 @@ export async function addUserAction(
|
||||||
.values({
|
.values({
|
||||||
userId,
|
userId,
|
||||||
actionId,
|
actionId,
|
||||||
orgId,
|
orgId
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
@ -61,7 +63,7 @@ export async function addUserAction(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Action added to user successfully",
|
message: "Action added to user successfully",
|
||||||
status: HttpCode.CREATED,
|
status: HttpCode.CREATED
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -10,10 +10,12 @@ import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import stoi from "@server/utils/stoi";
|
import stoi from "@server/utils/stoi";
|
||||||
|
|
||||||
const addUserRoleParamsSchema = z.object({
|
const addUserRoleParamsSchema = z
|
||||||
|
.object({
|
||||||
userId: z.string(),
|
userId: z.string(),
|
||||||
roleId: z.string().transform(stoi).pipe(z.number()),
|
roleId: z.string().transform(stoi).pipe(z.number())
|
||||||
});
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export type AddUserRoleResponse = z.infer<typeof addUserRoleParamsSchema>;
|
export type AddUserRoleResponse = z.infer<typeof addUserRoleParamsSchema>;
|
||||||
|
|
||||||
|
@ -96,7 +98,7 @@ export async function addUserRole(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Role added to user successfully",
|
message: "Role added to user successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -9,10 +9,12 @@ import logger from "@server/logger";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
|
|
||||||
const addUserSiteSchema = z.object({
|
const addUserSiteSchema = z
|
||||||
|
.object({
|
||||||
userId: z.string(),
|
userId: z.string(),
|
||||||
siteId: z.string().transform(Number).pipe(z.number().int().positive()),
|
siteId: z.string().transform(Number).pipe(z.number().int().positive())
|
||||||
});
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export async function addUserSite(
|
export async function addUserSite(
|
||||||
req: Request,
|
req: Request,
|
||||||
|
@ -36,7 +38,7 @@ export async function addUserSite(
|
||||||
.insert(userSites)
|
.insert(userSites)
|
||||||
.values({
|
.values({
|
||||||
userId,
|
userId,
|
||||||
siteId,
|
siteId
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
@ -48,7 +50,7 @@ export async function addUserSite(
|
||||||
for (const resource of siteResources) {
|
for (const resource of siteResources) {
|
||||||
await db.insert(userResources).values({
|
await db.insert(userResources).values({
|
||||||
userId,
|
userId,
|
||||||
resourceId: resource.resourceId,
|
resourceId: resource.resourceId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,7 +59,7 @@ export async function addUserSite(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Site added to user successfully",
|
message: "Site added to user successfully",
|
||||||
status: HttpCode.CREATED,
|
status: HttpCode.CREATED
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -19,7 +19,7 @@ async function queryUser(orgId: string, userId: string) {
|
||||||
roleId: userOrgs.roleId,
|
roleId: userOrgs.roleId,
|
||||||
roleName: roles.name,
|
roleName: roles.name,
|
||||||
isOwner: userOrgs.isOwner,
|
isOwner: userOrgs.isOwner,
|
||||||
isAdmin: roles.isAdmin,
|
isAdmin: roles.isAdmin
|
||||||
})
|
})
|
||||||
.from(userOrgs)
|
.from(userOrgs)
|
||||||
.leftJoin(roles, eq(userOrgs.roleId, roles.roleId))
|
.leftJoin(roles, eq(userOrgs.roleId, roles.roleId))
|
||||||
|
@ -33,10 +33,12 @@ export type GetOrgUserResponse = NonNullable<
|
||||||
Awaited<ReturnType<typeof queryUser>>
|
Awaited<ReturnType<typeof queryUser>>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
const getOrgUserParamsSchema = z.object({
|
const getOrgUserParamsSchema = z
|
||||||
|
.object({
|
||||||
userId: z.string(),
|
userId: z.string(),
|
||||||
orgId: z.string(),
|
orgId: z.string()
|
||||||
});
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export async function getOrgUser(
|
export async function getOrgUser(
|
||||||
req: Request,
|
req: Request,
|
||||||
|
@ -109,7 +111,7 @@ export async function getOrgUser(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "User retrieved successfully",
|
message: "User retrieved successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -6,7 +6,6 @@ import { and, eq } from "drizzle-orm";
|
||||||
import response from "@server/utils/response";
|
import response from "@server/utils/response";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions";
|
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { alphabet, generateRandomString } from "oslo/crypto";
|
import { alphabet, generateRandomString } from "oslo/crypto";
|
||||||
import { createDate, TimeSpan } from "oslo";
|
import { createDate, TimeSpan } from "oslo";
|
||||||
|
@ -16,15 +15,20 @@ import { fromError } from "zod-validation-error";
|
||||||
import { sendEmail } from "@server/emails";
|
import { sendEmail } from "@server/emails";
|
||||||
import SendInviteLink from "@server/emails/templates/SendInviteLink";
|
import SendInviteLink from "@server/emails/templates/SendInviteLink";
|
||||||
|
|
||||||
const inviteUserParamsSchema = z.object({
|
const inviteUserParamsSchema = z
|
||||||
orgId: z.string(),
|
.object({
|
||||||
});
|
orgId: z.string()
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
const inviteUserBodySchema = z.object({
|
const inviteUserBodySchema = z
|
||||||
|
.object({
|
||||||
email: z.string().email(),
|
email: z.string().email(),
|
||||||
roleId: z.number(),
|
roleId: z.number(),
|
||||||
validHours: z.number().gt(0).lte(168),
|
validHours: z.number().gt(0).lte(168),
|
||||||
});
|
sendEmail: z.boolean().optional()
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export type InviteUserBody = z.infer<typeof inviteUserBodySchema>;
|
export type InviteUserBody = z.infer<typeof inviteUserBodySchema>;
|
||||||
|
|
||||||
|
@ -38,7 +42,7 @@ const inviteTracker: Record<string, { timestamps: number[] }> = {};
|
||||||
export async function inviteUser(
|
export async function inviteUser(
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
next: NextFunction,
|
next: NextFunction
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const parsedParams = inviteUserParamsSchema.safeParse(req.params);
|
const parsedParams = inviteUserParamsSchema.safeParse(req.params);
|
||||||
|
@ -46,8 +50,8 @@ export async function inviteUser(
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
fromError(parsedParams.error).toString(),
|
fromError(parsedParams.error).toString()
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,13 +60,18 @@ export async function inviteUser(
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
fromError(parsedBody.error).toString(),
|
fromError(parsedBody.error).toString()
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { orgId } = parsedParams.data;
|
const { orgId } = parsedParams.data;
|
||||||
const { email, validHours, roleId } = parsedBody.data;
|
const {
|
||||||
|
email,
|
||||||
|
validHours,
|
||||||
|
roleId,
|
||||||
|
sendEmail: doEmail
|
||||||
|
} = parsedBody.data;
|
||||||
|
|
||||||
const currentTime = Date.now();
|
const currentTime = Date.now();
|
||||||
const oneHourAgo = currentTime - 3600000;
|
const oneHourAgo = currentTime - 3600000;
|
||||||
|
@ -79,8 +88,8 @@ export async function inviteUser(
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.TOO_MANY_REQUESTS,
|
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);
|
.limit(1);
|
||||||
if (!org.length) {
|
if (!org.length) {
|
||||||
return next(
|
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(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
"User is already a member of this organization",
|
"User is already a member of this organization"
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const inviteId = generateRandomString(
|
const inviteId = generateRandomString(
|
||||||
10,
|
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 token = generateRandomString(32, alphabet("a-z", "A-Z", "0-9"));
|
||||||
const expiresAt = createDate(new TimeSpan(validHours, "h")).getTime();
|
const expiresAt = createDate(new TimeSpan(validHours, "h")).getTime();
|
||||||
|
@ -125,7 +134,7 @@ export async function inviteUser(
|
||||||
await db
|
await db
|
||||||
.delete(userInvites)
|
.delete(userInvites)
|
||||||
.where(
|
.where(
|
||||||
and(eq(userInvites.email, email), eq(userInvites.orgId, orgId)),
|
and(eq(userInvites.email, email), eq(userInvites.orgId, orgId))
|
||||||
)
|
)
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
|
@ -135,43 +144,42 @@ export async function inviteUser(
|
||||||
email,
|
email,
|
||||||
expiresAt,
|
expiresAt,
|
||||||
tokenHash,
|
tokenHash,
|
||||||
roleId,
|
roleId
|
||||||
});
|
});
|
||||||
|
|
||||||
const inviteLink = `${config.app.base_url}/invite?token=${inviteId}-${token}`;
|
const inviteLink = `${config.app.base_url}/invite?token=${inviteId}-${token}`;
|
||||||
|
|
||||||
|
if (doEmail) {
|
||||||
await sendEmail(
|
await sendEmail(
|
||||||
SendInviteLink({
|
SendInviteLink({
|
||||||
email,
|
email,
|
||||||
inviteLink,
|
inviteLink,
|
||||||
expiresInDays: (validHours / 24).toString(),
|
expiresInDays: (validHours / 24).toString(),
|
||||||
orgName: org[0].name || orgId,
|
orgName: org[0].name || orgId,
|
||||||
inviterName: req.user?.email,
|
inviterName: req.user?.email
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
to: email,
|
to: email,
|
||||||
from: config.email?.no_reply,
|
from: config.email?.no_reply,
|
||||||
subject: "You're invited to join a Fossorial organization",
|
subject: "You're invited to join a Fossorial organization"
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return response<InviteUserResponse>(res, {
|
return response<InviteUserResponse>(res, {
|
||||||
data: {
|
data: {
|
||||||
inviteLink,
|
inviteLink,
|
||||||
expiresAt,
|
expiresAt
|
||||||
},
|
},
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "User invited successfully",
|
message: "User invited successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
logger.error(error);
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
|
||||||
"An error occurred",
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,11 +8,14 @@ import createHttpError from "http-errors";
|
||||||
import { sql } from "drizzle-orm";
|
import { sql } from "drizzle-orm";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
|
|
||||||
const listUsersParamsSchema = z.object({
|
const listUsersParamsSchema = z
|
||||||
orgId: z.string(),
|
.object({
|
||||||
});
|
orgId: z.string()
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
const listUsersSchema = z.object({
|
const listUsersSchema = z
|
||||||
|
.object({
|
||||||
limit: z
|
limit: z
|
||||||
.string()
|
.string()
|
||||||
.optional()
|
.optional()
|
||||||
|
@ -24,8 +27,9 @@ const listUsersSchema = z.object({
|
||||||
.optional()
|
.optional()
|
||||||
.default("0")
|
.default("0")
|
||||||
.transform(Number)
|
.transform(Number)
|
||||||
.pipe(z.number().int().nonnegative()),
|
.pipe(z.number().int().nonnegative())
|
||||||
});
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
async function queryUsers(orgId: string, limit: number, offset: number) {
|
async function queryUsers(orgId: string, limit: number, offset: number) {
|
||||||
return await db
|
return await db
|
||||||
|
@ -37,7 +41,7 @@ async function queryUsers(orgId: string, limit: number, offset: number) {
|
||||||
orgId: userOrgs.orgId,
|
orgId: userOrgs.orgId,
|
||||||
roleId: userOrgs.roleId,
|
roleId: userOrgs.roleId,
|
||||||
roleName: roles.name,
|
roleName: roles.name,
|
||||||
isOwner: userOrgs.isOwner,
|
isOwner: userOrgs.isOwner
|
||||||
})
|
})
|
||||||
.from(users)
|
.from(users)
|
||||||
.leftJoin(userOrgs, sql`${users.userId} = ${userOrgs.userId}`)
|
.leftJoin(userOrgs, sql`${users.userId} = ${userOrgs.userId}`)
|
||||||
|
@ -97,13 +101,13 @@ export async function listUsers(
|
||||||
pagination: {
|
pagination: {
|
||||||
total: count,
|
total: count,
|
||||||
limit,
|
limit,
|
||||||
offset,
|
offset
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Users retrieved successfully",
|
message: "Users retrieved successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -9,14 +9,18 @@ import createHttpError from "http-errors";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
|
|
||||||
const removeUserActionParamsSchema = z.object({
|
const removeUserActionParamsSchema = z
|
||||||
userId: z.string(),
|
.object({
|
||||||
});
|
userId: z.string()
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
const removeUserActionSchema = z.object({
|
const removeUserActionSchema = z
|
||||||
|
.object({
|
||||||
actionId: z.string(),
|
actionId: z.string(),
|
||||||
orgId: z.string(),
|
orgId: z.string()
|
||||||
});
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export async function removeUserAction(
|
export async function removeUserAction(
|
||||||
req: Request,
|
req: Request,
|
||||||
|
@ -73,7 +77,7 @@ export async function removeUserAction(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Action removed from user successfully",
|
message: "Action removed from user successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -9,10 +9,12 @@ import createHttpError from "http-errors";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
|
|
||||||
const removeUserSchema = z.object({
|
const removeUserSchema = z
|
||||||
|
.object({
|
||||||
userId: z.string(),
|
userId: z.string(),
|
||||||
orgId: z.string(),
|
orgId: z.string()
|
||||||
});
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export async function removeUserOrg(
|
export async function removeUserOrg(
|
||||||
req: Request,
|
req: Request,
|
||||||
|
@ -70,7 +72,7 @@ export async function removeUserOrg(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "User remove from org successfully",
|
message: "User remove from org successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -9,10 +9,15 @@ import createHttpError from "http-errors";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
|
|
||||||
const removeUserResourceSchema = z.object({
|
const removeUserResourceSchema = z
|
||||||
|
.object({
|
||||||
userId: z.string(),
|
userId: z.string(),
|
||||||
resourceId: z.string().transform(Number).pipe(z.number().int().positive()),
|
resourceId: z
|
||||||
});
|
.string()
|
||||||
|
.transform(Number)
|
||||||
|
.pipe(z.number().int().positive())
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export async function removeUserResource(
|
export async function removeUserResource(
|
||||||
req: Request,
|
req: Request,
|
||||||
|
@ -56,7 +61,7 @@ export async function removeUserResource(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Resource removed from user successfully",
|
message: "Resource removed from user successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -9,13 +9,17 @@ import createHttpError from "http-errors";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
|
|
||||||
const removeUserSiteParamsSchema = z.object({
|
const removeUserSiteParamsSchema = z
|
||||||
userId: z.string(),
|
.object({
|
||||||
});
|
userId: z.string()
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
const removeUserSiteSchema = z.object({
|
const removeUserSiteSchema = z
|
||||||
siteId: z.number().int().positive(),
|
.object({
|
||||||
});
|
siteId: z.number().int().positive()
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export async function removeUserSite(
|
export async function removeUserSite(
|
||||||
req: Request,
|
req: Request,
|
||||||
|
@ -85,7 +89,7 @@ export async function removeUserSite(
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Site removed from user successfully",
|
message: "Site removed from user successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -126,7 +126,7 @@ export default function CreateRoleForm({
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
className="space-y-8:w"
|
className="space-y-8"
|
||||||
id="create-role-form"
|
id="create-role-form"
|
||||||
>
|
>
|
||||||
<FormField
|
<FormField
|
||||||
|
|
|
@ -5,7 +5,7 @@ import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger
|
||||||
} from "@app/components/ui/dropdown-menu";
|
} from "@app/components/ui/dropdown-menu";
|
||||||
import { Button } from "@app/components/ui/button";
|
import { Button } from "@app/components/ui/button";
|
||||||
import { ArrowUpDown, Crown, MoreHorizontal } from "lucide-react";
|
import { ArrowUpDown, Crown, MoreHorizontal } from "lucide-react";
|
||||||
|
@ -54,11 +54,11 @@ export default function UsersTable({ roles: r }: RolesTableProps) {
|
||||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "description",
|
accessorKey: "description",
|
||||||
header: "Description",
|
header: "Description"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "actions",
|
id: "actions",
|
||||||
|
@ -90,16 +90,15 @@ export default function UsersTable({ roles: r }: RolesTableProps) {
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end">
|
<DropdownMenuContent align="end">
|
||||||
<DropdownMenuItem>
|
<DropdownMenuItem
|
||||||
<button
|
|
||||||
className="text-red-500"
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsDeleteModalOpen(true);
|
setIsDeleteModalOpen(true);
|
||||||
setUserToRemove(roleRow);
|
setUserToRemove(roleRow);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<span className="text-red-500">
|
||||||
Delete Role
|
Delete Role
|
||||||
</button>
|
</span>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
@ -107,8 +106,8 @@ export default function UsersTable({ roles: r }: RolesTableProps) {
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -128,9 +127,7 @@ export default function UsersTable({ roles: r }: RolesTableProps) {
|
||||||
roleToDelete={roleToRemove}
|
roleToDelete={roleToRemove}
|
||||||
afterDelete={() => {
|
afterDelete={() => {
|
||||||
setRoles((prev) =>
|
setRoles((prev) =>
|
||||||
prev.filter(
|
prev.filter((r) => r.roleId !== roleToRemove.roleId)
|
||||||
(r) => r.roleId !== roleToRemove.roleId,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
setUserToRemove(null);
|
setUserToRemove(null);
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
FormField,
|
FormField,
|
||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage
|
||||||
} from "@app/components/ui/form";
|
} from "@app/components/ui/form";
|
||||||
import { Input } from "@app/components/ui/input";
|
import { Input } from "@app/components/ui/input";
|
||||||
import {
|
import {
|
||||||
|
@ -15,7 +15,7 @@ import {
|
||||||
SelectContent,
|
SelectContent,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue
|
||||||
} from "@app/components/ui/select";
|
} from "@app/components/ui/select";
|
||||||
import { useToast } from "@app/hooks/useToast";
|
import { useToast } from "@app/hooks/useToast";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
@ -33,13 +33,14 @@ import {
|
||||||
CredenzaDescription,
|
CredenzaDescription,
|
||||||
CredenzaFooter,
|
CredenzaFooter,
|
||||||
CredenzaHeader,
|
CredenzaHeader,
|
||||||
CredenzaTitle,
|
CredenzaTitle
|
||||||
} from "@app/components/Credenza";
|
} from "@app/components/Credenza";
|
||||||
import { useOrgContext } from "@app/hooks/useOrgContext";
|
import { useOrgContext } from "@app/hooks/useOrgContext";
|
||||||
import { ListRolesResponse } from "@server/routers/role";
|
import { ListRolesResponse } from "@server/routers/role";
|
||||||
import { formatAxiosError } from "@app/lib/utils";
|
import { formatAxiosError } from "@app/lib/utils";
|
||||||
import { createApiClient } from "@app/api";
|
import { createApiClient } from "@app/api";
|
||||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||||
|
import { Checkbox } from "@app/components/ui/checkbox";
|
||||||
|
|
||||||
type InviteUserFormProps = {
|
type InviteUserFormProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
@ -49,14 +50,16 @@ type InviteUserFormProps = {
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
email: z.string().email({ message: "Invalid email address" }),
|
email: z.string().email({ message: "Invalid email address" }),
|
||||||
validForHours: z.string().min(1, { message: "Please select a duration" }),
|
validForHours: z.string().min(1, { message: "Please select a duration" }),
|
||||||
roleId: z.string().min(1, { message: "Please select a role" }),
|
roleId: z.string().min(1, { message: "Please select a role" })
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function InviteUserForm({ open, setOpen }: InviteUserFormProps) {
|
export default function InviteUserForm({ open, setOpen }: InviteUserFormProps) {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { org } = useOrgContext();
|
const { org } = useOrgContext();
|
||||||
|
|
||||||
const api = createApiClient(useEnvContext());
|
const { env } = useEnvContext();
|
||||||
|
|
||||||
|
const api = createApiClient({ env });
|
||||||
|
|
||||||
const [inviteLink, setInviteLink] = useState<string | null>(null);
|
const [inviteLink, setInviteLink] = useState<string | null>(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
@ -64,6 +67,8 @@ export default function InviteUserForm({ open, setOpen }: InviteUserFormProps) {
|
||||||
|
|
||||||
const [roles, setRoles] = useState<{ roleId: number; name: string }[]>([]);
|
const [roles, setRoles] = useState<{ roleId: number; name: string }[]>([]);
|
||||||
|
|
||||||
|
const [sendEmail, setSendEmail] = useState(env.EMAIL_ENABLED === "true");
|
||||||
|
|
||||||
const validFor = [
|
const validFor = [
|
||||||
{ hours: 24, name: "1 day" },
|
{ hours: 24, name: "1 day" },
|
||||||
{ hours: 48, name: "2 days" },
|
{ hours: 48, name: "2 days" },
|
||||||
|
@ -71,7 +76,7 @@ export default function InviteUserForm({ open, setOpen }: InviteUserFormProps) {
|
||||||
{ hours: 96, name: "4 days" },
|
{ hours: 96, name: "4 days" },
|
||||||
{ hours: 120, name: "5 days" },
|
{ hours: 120, name: "5 days" },
|
||||||
{ hours: 144, name: "6 days" },
|
{ hours: 144, name: "6 days" },
|
||||||
{ hours: 168, name: "7 days" },
|
{ hours: 168, name: "7 days" }
|
||||||
];
|
];
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
|
@ -79,8 +84,8 @@ export default function InviteUserForm({ open, setOpen }: InviteUserFormProps) {
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
email: "",
|
email: "",
|
||||||
validForHours: "72",
|
validForHours: "72",
|
||||||
roleId: "",
|
roleId: ""
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -90,9 +95,9 @@ export default function InviteUserForm({ open, setOpen }: InviteUserFormProps) {
|
||||||
|
|
||||||
async function fetchRoles() {
|
async function fetchRoles() {
|
||||||
const res = await api
|
const res = await api
|
||||||
.get<AxiosResponse<ListRolesResponse>>(
|
.get<
|
||||||
`/org/${org?.org.orgId}/roles`
|
AxiosResponse<ListRolesResponse>
|
||||||
)
|
>(`/org/${org?.org.orgId}/roles`)
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
toast({
|
toast({
|
||||||
|
@ -101,7 +106,7 @@ export default function InviteUserForm({ open, setOpen }: InviteUserFormProps) {
|
||||||
description: formatAxiosError(
|
description: formatAxiosError(
|
||||||
e,
|
e,
|
||||||
"An error occurred while fetching the roles"
|
"An error occurred while fetching the roles"
|
||||||
),
|
)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -127,6 +132,7 @@ export default function InviteUserForm({ open, setOpen }: InviteUserFormProps) {
|
||||||
email: values.email,
|
email: values.email,
|
||||||
roleId: parseInt(values.roleId),
|
roleId: parseInt(values.roleId),
|
||||||
validHours: parseInt(values.validForHours),
|
validHours: parseInt(values.validForHours),
|
||||||
|
sendEmail: sendEmail
|
||||||
} as InviteUserBody
|
} as InviteUserBody
|
||||||
)
|
)
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
|
@ -136,7 +142,7 @@ export default function InviteUserForm({ open, setOpen }: InviteUserFormProps) {
|
||||||
description: formatAxiosError(
|
description: formatAxiosError(
|
||||||
e,
|
e,
|
||||||
"An error occurred while inviting the user"
|
"An error occurred while inviting the user"
|
||||||
),
|
)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -145,7 +151,7 @@ export default function InviteUserForm({ open, setOpen }: InviteUserFormProps) {
|
||||||
toast({
|
toast({
|
||||||
variant: "default",
|
variant: "default",
|
||||||
title: "User invited",
|
title: "User invited",
|
||||||
description: "The user has been successfully invited.",
|
description: "The user has been successfully invited."
|
||||||
});
|
});
|
||||||
|
|
||||||
setExpiresInDays(parseInt(values.validForHours) / 24);
|
setExpiresInDays(parseInt(values.validForHours) / 24);
|
||||||
|
@ -198,6 +204,27 @@ export default function InviteUserForm({ open, setOpen }: InviteUserFormProps) {
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{env.EMAIL_ENABLED === "true" && (
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id="send-email"
|
||||||
|
checked={sendEmail}
|
||||||
|
onCheckedChange={(e) =>
|
||||||
|
setSendEmail(
|
||||||
|
e as boolean
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="send-email"
|
||||||
|
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
|
>
|
||||||
|
Send invite email to user
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="roleId"
|
name="roleId"
|
||||||
|
@ -281,11 +308,21 @@ export default function InviteUserForm({ open, setOpen }: InviteUserFormProps) {
|
||||||
|
|
||||||
{inviteLink && (
|
{inviteLink && (
|
||||||
<div className="max-w-md space-y-4">
|
<div className="max-w-md space-y-4">
|
||||||
|
{sendEmail && (
|
||||||
<p>
|
<p>
|
||||||
The user has been successfully invited.
|
An email has been sent to the user
|
||||||
They must access the link below to
|
with the access link below. They
|
||||||
accept the invitation.
|
must access the link to accept the
|
||||||
|
invitation.
|
||||||
</p>
|
</p>
|
||||||
|
)}
|
||||||
|
{!sendEmail && (
|
||||||
|
<p>
|
||||||
|
The user has been invited. They must
|
||||||
|
access the link below to accept the
|
||||||
|
invitation.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
<p>
|
<p>
|
||||||
The invite will expire in{" "}
|
The invite will expire in{" "}
|
||||||
<b>
|
<b>
|
||||||
|
|
|
@ -5,7 +5,7 @@ import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger
|
||||||
} from "@app/components/ui/dropdown-menu";
|
} from "@app/components/ui/dropdown-menu";
|
||||||
import { Button } from "@app/components/ui/button";
|
import { Button } from "@app/components/ui/button";
|
||||||
import { ArrowRight, ArrowUpDown, Crown, MoreHorizontal } from "lucide-react";
|
import { ArrowRight, ArrowUpDown, Crown, MoreHorizontal } from "lucide-react";
|
||||||
|
@ -64,7 +64,7 @@ export default function UsersTable({ users: u }: UsersTableProps) {
|
||||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "status",
|
accessorKey: "status",
|
||||||
|
@ -80,7 +80,7 @@ export default function UsersTable({ users: u }: UsersTableProps) {
|
||||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "role",
|
accessorKey: "role",
|
||||||
|
@ -108,7 +108,7 @@ export default function UsersTable({ users: u }: UsersTableProps) {
|
||||||
<span>{userRow.role}</span>
|
<span>{userRow.role}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "actions",
|
id: "actions",
|
||||||
|
@ -149,20 +149,19 @@ export default function UsersTable({ users: u }: UsersTableProps) {
|
||||||
</Link>
|
</Link>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
{userRow.email !== user?.email && (
|
{userRow.email !== user?.email && (
|
||||||
<DropdownMenuItem>
|
<DropdownMenuItem
|
||||||
<button
|
|
||||||
className="text-red-500"
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsDeleteModalOpen(
|
setIsDeleteModalOpen(
|
||||||
true,
|
true
|
||||||
);
|
);
|
||||||
setSelectedUser(
|
setSelectedUser(
|
||||||
userRow,
|
userRow
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<span className="text-red-500">
|
||||||
Remove User
|
Remove User
|
||||||
</button>
|
</span>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
|
@ -183,8 +182,8 @@ export default function UsersTable({ users: u }: UsersTableProps) {
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
async function removeUser() {
|
async function removeUser() {
|
||||||
|
@ -197,8 +196,8 @@ export default function UsersTable({ users: u }: UsersTableProps) {
|
||||||
title: "Failed to remove user",
|
title: "Failed to remove user",
|
||||||
description: formatAxiosError(
|
description: formatAxiosError(
|
||||||
e,
|
e,
|
||||||
"An error occurred while removing the user.",
|
"An error occurred while removing the user."
|
||||||
),
|
)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -206,11 +205,11 @@ export default function UsersTable({ users: u }: UsersTableProps) {
|
||||||
toast({
|
toast({
|
||||||
variant: "default",
|
variant: "default",
|
||||||
title: "User removed",
|
title: "User removed",
|
||||||
description: `The user ${selectedUser.email} has been removed from the organization.`,
|
description: `The user ${selectedUser.email} has been removed from the organization.`
|
||||||
});
|
});
|
||||||
|
|
||||||
setUsers((prev) =>
|
setUsers((prev) =>
|
||||||
prev.filter((u) => u.id !== selectedUser?.id),
|
prev.filter((u) => u.id !== selectedUser?.id)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -637,6 +637,7 @@ export default function ResourceAuthenticationPage() {
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{whitelistEnabled && (
|
||||||
<Form {...whitelistForm}>
|
<Form {...whitelistForm}>
|
||||||
<form className="space-y-8">
|
<form className="space-y-8">
|
||||||
<FormField
|
<FormField
|
||||||
|
@ -653,12 +654,15 @@ export default function ResourceAuthenticationPage() {
|
||||||
activeTagIndex={
|
activeTagIndex={
|
||||||
activeEmailTagIndex
|
activeEmailTagIndex
|
||||||
}
|
}
|
||||||
validateTag={(tag) => {
|
validateTag={(
|
||||||
|
tag
|
||||||
|
) => {
|
||||||
return z
|
return z
|
||||||
.string()
|
.string()
|
||||||
.email()
|
.email()
|
||||||
.safeParse(tag)
|
.safeParse(
|
||||||
.success;
|
tag
|
||||||
|
).success;
|
||||||
}}
|
}}
|
||||||
setActiveTagIndex={
|
setActiveTagIndex={
|
||||||
setActiveEmailTagIndex
|
setActiveEmailTagIndex
|
||||||
|
@ -668,7 +672,9 @@ export default function ResourceAuthenticationPage() {
|
||||||
whitelistForm.getValues()
|
whitelistForm.getValues()
|
||||||
.emails
|
.emails
|
||||||
}
|
}
|
||||||
setTags={(newRoles) => {
|
setTags={(
|
||||||
|
newRoles
|
||||||
|
) => {
|
||||||
whitelistForm.setValue(
|
whitelistForm.setValue(
|
||||||
"emails",
|
"emails",
|
||||||
newRoles as [
|
newRoles as [
|
||||||
|
@ -677,7 +683,9 @@ export default function ResourceAuthenticationPage() {
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
allowDuplicates={false}
|
allowDuplicates={
|
||||||
|
false
|
||||||
|
}
|
||||||
sortTags={true}
|
sortTags={true}
|
||||||
styleClasses={{
|
styleClasses={{
|
||||||
tag: {
|
tag: {
|
||||||
|
@ -694,6 +702,7 @@ export default function ResourceAuthenticationPage() {
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
|
)}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
loading={loadingSaveWhitelist}
|
loading={loadingSaveWhitelist}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger
|
||||||
} from "@app/components/ui/dropdown-menu";
|
} from "@app/components/ui/dropdown-menu";
|
||||||
import { Button } from "@app/components/ui/button";
|
import { Button } from "@app/components/ui/button";
|
||||||
import {
|
import {
|
||||||
|
@ -17,7 +17,7 @@ import {
|
||||||
Check,
|
Check,
|
||||||
ArrowUpRight,
|
ArrowUpRight,
|
||||||
ShieldOff,
|
ShieldOff,
|
||||||
ShieldCheck,
|
ShieldCheck
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
@ -64,7 +64,7 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||||
toast({
|
toast({
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
title: "Error deleting resource",
|
title: "Error deleting resource",
|
||||||
description: formatAxiosError(e, "Error deleting resource"),
|
description: formatAxiosError(e, "Error deleting resource")
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
@ -88,7 +88,7 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "site",
|
accessorKey: "site",
|
||||||
|
@ -117,7 +117,7 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||||
<ArrowUpRight className="ml-2 h-4 w-4" />
|
<ArrowUpRight className="ml-2 h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "domain",
|
accessorKey: "domain",
|
||||||
|
@ -139,16 +139,16 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||||
className="h-6 w-6 p-0"
|
className="h-6 w-6 p-0"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigator.clipboard.writeText(
|
navigator.clipboard.writeText(
|
||||||
resourceRow.domain,
|
resourceRow.domain
|
||||||
);
|
);
|
||||||
const originalIcon = document.querySelector(
|
const originalIcon = document.querySelector(
|
||||||
`#icon-${resourceRow.id}`,
|
`#icon-${resourceRow.id}`
|
||||||
);
|
);
|
||||||
if (originalIcon) {
|
if (originalIcon) {
|
||||||
originalIcon.classList.add("hidden");
|
originalIcon.classList.add("hidden");
|
||||||
}
|
}
|
||||||
const checkIcon = document.querySelector(
|
const checkIcon = document.querySelector(
|
||||||
`#check-icon-${resourceRow.id}`,
|
`#check-icon-${resourceRow.id}`
|
||||||
);
|
);
|
||||||
if (checkIcon) {
|
if (checkIcon) {
|
||||||
checkIcon.classList.remove("hidden");
|
checkIcon.classList.remove("hidden");
|
||||||
|
@ -156,7 +156,7 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||||
checkIcon.classList.add("hidden");
|
checkIcon.classList.add("hidden");
|
||||||
if (originalIcon) {
|
if (originalIcon) {
|
||||||
originalIcon.classList.remove(
|
originalIcon.classList.remove(
|
||||||
"hidden",
|
"hidden"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, 2000);
|
}, 2000);
|
||||||
|
@ -175,7 +175,7 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "hasAuth",
|
accessorKey: "hasAuth",
|
||||||
|
@ -209,7 +209,7 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "actions",
|
id: "actions",
|
||||||
|
@ -241,18 +241,15 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||||
View settings
|
View settings
|
||||||
</Link>
|
</Link>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem>
|
<DropdownMenuItem
|
||||||
<button
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedResource(
|
setSelectedResource(resourceRow);
|
||||||
resourceRow,
|
|
||||||
);
|
|
||||||
setIsDeleteModalOpen(true);
|
setIsDeleteModalOpen(true);
|
||||||
}}
|
}}
|
||||||
className="text-red-500"
|
|
||||||
>
|
>
|
||||||
|
<span className="text-red-500">
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</span>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
@ -267,8 +264,8 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -197,12 +197,16 @@ export default function CreateShareLinkForm({
|
||||||
const link = constructShareLink(
|
const link = constructShareLink(
|
||||||
values.resourceId,
|
values.resourceId,
|
||||||
token.accessTokenId,
|
token.accessTokenId,
|
||||||
token.tokenHash
|
token.accessToken
|
||||||
);
|
);
|
||||||
setLink(link);
|
setLink(link);
|
||||||
onCreated?.({
|
onCreated?.({
|
||||||
...token,
|
accessTokenId: token.accessTokenId,
|
||||||
resourceName: values.resourceName
|
resourceId: token.resourceId,
|
||||||
|
resourceName: values.resourceName,
|
||||||
|
title: token.title,
|
||||||
|
createdAt: token.createdAt,
|
||||||
|
expiresAt: token.expiresAt
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -285,7 +289,9 @@ export default function CreateShareLinkForm({
|
||||||
r
|
r
|
||||||
) => (
|
) => (
|
||||||
<CommandItem
|
<CommandItem
|
||||||
value={r.name}
|
value={
|
||||||
|
r.name
|
||||||
|
}
|
||||||
key={
|
key={
|
||||||
r.resourceId
|
r.resourceId
|
||||||
}
|
}
|
||||||
|
@ -441,6 +447,10 @@ export default function CreateShareLinkForm({
|
||||||
)}
|
)}
|
||||||
{link && (
|
{link && (
|
||||||
<div className="max-w-md space-y-4">
|
<div className="max-w-md space-y-4">
|
||||||
|
<p>
|
||||||
|
You will be able to see this link once.
|
||||||
|
Make sure to copy it.
|
||||||
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Anyone with this link can access the
|
Anyone with this link can access the
|
||||||
resource. Share it with care.
|
resource. Share it with care.
|
||||||
|
|
|
@ -34,9 +34,14 @@ import moment from "moment";
|
||||||
import CreateShareLinkForm from "./CreateShareLinkForm";
|
import CreateShareLinkForm from "./CreateShareLinkForm";
|
||||||
import { constructShareLink } from "@app/lib/shareLinks";
|
import { constructShareLink } from "@app/lib/shareLinks";
|
||||||
|
|
||||||
export type ShareLinkRow = ArrayElement<
|
export type ShareLinkRow = {
|
||||||
ListAccessTokensResponse["accessTokens"]
|
accessTokenId: string;
|
||||||
>;
|
resourceId: number;
|
||||||
|
resourceName: string;
|
||||||
|
title: string | null;
|
||||||
|
createdAt: number;
|
||||||
|
expiresAt: number | null;
|
||||||
|
};
|
||||||
|
|
||||||
type ShareLinksTableProps = {
|
type ShareLinksTableProps = {
|
||||||
shareLinks: ShareLinkRow[];
|
shareLinks: ShareLinkRow[];
|
||||||
|
@ -64,7 +69,10 @@ export default function ShareLinksTable({
|
||||||
await api.delete(`/access-token/${id}`).catch((e) => {
|
await api.delete(`/access-token/${id}`).catch((e) => {
|
||||||
toast({
|
toast({
|
||||||
title: "Failed to delete link",
|
title: "Failed to delete link",
|
||||||
description: formatAxiosError(e, "An error occurred deleting link"),
|
description: formatAxiosError(
|
||||||
|
e,
|
||||||
|
"An error occurred deleting link"
|
||||||
|
)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -73,7 +81,7 @@ export default function ShareLinksTable({
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "Link deleted",
|
title: "Link deleted",
|
||||||
description: "The link has been deleted",
|
description: "The link has been deleted"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,69 +131,69 @@ export default function ShareLinksTable({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
accessorKey: "domain",
|
// accessorKey: "domain",
|
||||||
header: "Link",
|
// header: "Link",
|
||||||
cell: ({ row }) => {
|
// cell: ({ row }) => {
|
||||||
const r = row.original;
|
// const r = row.original;
|
||||||
|
//
|
||||||
const link = constructShareLink(
|
// const link = constructShareLink(
|
||||||
r.resourceId,
|
// r.resourceId,
|
||||||
r.accessTokenId,
|
// r.accessTokenId,
|
||||||
r.tokenHash
|
// r.tokenHash
|
||||||
);
|
// );
|
||||||
|
//
|
||||||
return (
|
// return (
|
||||||
<div className="flex items-center">
|
// <div className="flex items-center">
|
||||||
<Link
|
// <Link
|
||||||
href={link}
|
// href={link}
|
||||||
target="_blank"
|
// target="_blank"
|
||||||
rel="noopener noreferrer"
|
// rel="noopener noreferrer"
|
||||||
className="hover:underline mr-2"
|
// className="hover:underline mr-2"
|
||||||
>
|
// >
|
||||||
{formatLink(link)}
|
// {formatLink(link)}
|
||||||
</Link>
|
// </Link>
|
||||||
<Button
|
// <Button
|
||||||
variant="ghost"
|
// variant="ghost"
|
||||||
className="h-6 w-6 p-0"
|
// className="h-6 w-6 p-0"
|
||||||
onClick={() => {
|
// onClick={() => {
|
||||||
navigator.clipboard.writeText(link);
|
// navigator.clipboard.writeText(link);
|
||||||
const originalIcon = document.querySelector(
|
// const originalIcon = document.querySelector(
|
||||||
`#icon-${r.accessTokenId}`
|
// `#icon-${r.accessTokenId}`
|
||||||
);
|
// );
|
||||||
if (originalIcon) {
|
// if (originalIcon) {
|
||||||
originalIcon.classList.add("hidden");
|
// originalIcon.classList.add("hidden");
|
||||||
}
|
// }
|
||||||
const checkIcon = document.querySelector(
|
// const checkIcon = document.querySelector(
|
||||||
`#check-icon-${r.accessTokenId}`
|
// `#check-icon-${r.accessTokenId}`
|
||||||
);
|
// );
|
||||||
if (checkIcon) {
|
// if (checkIcon) {
|
||||||
checkIcon.classList.remove("hidden");
|
// checkIcon.classList.remove("hidden");
|
||||||
setTimeout(() => {
|
// setTimeout(() => {
|
||||||
checkIcon.classList.add("hidden");
|
// checkIcon.classList.add("hidden");
|
||||||
if (originalIcon) {
|
// if (originalIcon) {
|
||||||
originalIcon.classList.remove(
|
// originalIcon.classList.remove(
|
||||||
"hidden"
|
// "hidden"
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
}, 2000);
|
// }, 2000);
|
||||||
}
|
// }
|
||||||
}}
|
// }}
|
||||||
>
|
// >
|
||||||
<Copy
|
// <Copy
|
||||||
id={`icon-${r.accessTokenId}`}
|
// id={`icon-${r.accessTokenId}`}
|
||||||
className="h-4 w-4"
|
// className="h-4 w-4"
|
||||||
/>
|
// />
|
||||||
<Check
|
// <Check
|
||||||
id={`check-icon-${r.accessTokenId}`}
|
// id={`check-icon-${r.accessTokenId}`}
|
||||||
className="hidden text-green-500 h-4 w-4"
|
// className="hidden text-green-500 h-4 w-4"
|
||||||
/>
|
// />
|
||||||
<span className="sr-only">Copy link</span>
|
// <span className="sr-only">Copy link</span>
|
||||||
</Button>
|
// </Button>
|
||||||
</div>
|
// </div>
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
accessorKey: "createdAt",
|
accessorKey: "createdAt",
|
||||||
header: ({ column }) => {
|
header: ({ column }) => {
|
||||||
|
|
|
@ -46,9 +46,9 @@ export default async function ShareLinksPage(props: ShareLinksPageProps) {
|
||||||
redirect(`/${params.orgId}/settings/resources`);
|
redirect(`/${params.orgId}/settings/resources`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const rows: ShareLinkRow[] = tokens.map((token) => {
|
const rows: ShareLinkRow[] = tokens.map(
|
||||||
return token;
|
(token) => ({ ...token }) as ShareLinkRow
|
||||||
});
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
FormField,
|
FormField,
|
||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage
|
||||||
} from "@app/components/ui/form";
|
} from "@app/components/ui/form";
|
||||||
import { Input } from "@app/components/ui/input";
|
import { Input } from "@app/components/ui/input";
|
||||||
import { useToast } from "@app/hooks/useToast";
|
import { useToast } from "@app/hooks/useToast";
|
||||||
|
@ -24,11 +24,15 @@ import {
|
||||||
CredenzaDescription,
|
CredenzaDescription,
|
||||||
CredenzaFooter,
|
CredenzaFooter,
|
||||||
CredenzaHeader,
|
CredenzaHeader,
|
||||||
CredenzaTitle,
|
CredenzaTitle
|
||||||
} from "@app/components/Credenza";
|
} from "@app/components/Credenza";
|
||||||
import { useOrgContext } from "@app/hooks/useOrgContext";
|
import { useOrgContext } from "@app/hooks/useOrgContext";
|
||||||
import { useParams, useRouter } from "next/navigation";
|
import { useParams, useRouter } from "next/navigation";
|
||||||
import { CreateSiteBody, PickSiteDefaultsResponse } from "@server/routers/site";
|
import {
|
||||||
|
CreateSiteBody,
|
||||||
|
CreateSiteResponse,
|
||||||
|
PickSiteDefaultsResponse
|
||||||
|
} from "@server/routers/site";
|
||||||
import { generateKeypair } from "../[niceId]/components/wireguardConfig";
|
import { generateKeypair } from "../[niceId]/components/wireguardConfig";
|
||||||
import CopyTextBox from "@app/components/CopyTextBox";
|
import CopyTextBox from "@app/components/CopyTextBox";
|
||||||
import { Checkbox } from "@app/components/ui/checkbox";
|
import { Checkbox } from "@app/components/ui/checkbox";
|
||||||
|
@ -37,42 +41,49 @@ import {
|
||||||
SelectContent,
|
SelectContent,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue
|
||||||
} from "@app/components/ui/select";
|
} from "@app/components/ui/select";
|
||||||
import { formatAxiosError } from "@app/lib/utils";
|
import { formatAxiosError } from "@app/lib/utils";
|
||||||
import { createApiClient } from "@app/api";
|
import { createApiClient } from "@app/api";
|
||||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||||
|
import { SiteRow } from "./SitesTable";
|
||||||
|
import { AxiosResponse } from "axios";
|
||||||
|
|
||||||
const method = [
|
const method = [
|
||||||
{ label: "Newt", value: "newt" },
|
{ label: "Newt", value: "newt" },
|
||||||
{ label: "Wireguard", value: "wireguard" },
|
{ label: "WireGuard", value: "wireguard" }
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
const accountFormSchema = z.object({
|
const createSiteFormSchema = z.object({
|
||||||
name: z
|
name: z
|
||||||
.string()
|
.string()
|
||||||
.min(2, {
|
.min(2, {
|
||||||
message: "Name must be at least 2 characters.",
|
message: "Name must be at least 2 characters."
|
||||||
})
|
})
|
||||||
.max(30, {
|
.max(30, {
|
||||||
message: "Name must not be longer than 30 characters.",
|
message: "Name must not be longer than 30 characters."
|
||||||
}),
|
}),
|
||||||
method: z.enum(["wireguard", "newt"]),
|
method: z.enum(["wireguard", "newt"])
|
||||||
});
|
});
|
||||||
|
|
||||||
type AccountFormValues = z.infer<typeof accountFormSchema>;
|
type CreateSiteFormValues = z.infer<typeof createSiteFormSchema>;
|
||||||
|
|
||||||
const defaultValues: Partial<AccountFormValues> = {
|
const defaultValues: Partial<CreateSiteFormValues> = {
|
||||||
name: "",
|
name: "",
|
||||||
method: "newt",
|
method: "newt"
|
||||||
};
|
};
|
||||||
|
|
||||||
type CreateSiteFormProps = {
|
type CreateSiteFormProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
setOpen: (open: boolean) => void;
|
setOpen: (open: boolean) => void;
|
||||||
|
onCreate?: (site: SiteRow) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function CreateSiteForm({ open, setOpen }: CreateSiteFormProps) {
|
export default function CreateSiteForm({
|
||||||
|
open,
|
||||||
|
setOpen,
|
||||||
|
onCreate
|
||||||
|
}: CreateSiteFormProps) {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
const api = createApiClient(useEnvContext());
|
const api = createApiClient(useEnvContext());
|
||||||
|
@ -96,9 +107,9 @@ export default function CreateSiteForm({ open, setOpen }: CreateSiteFormProps) {
|
||||||
setIsChecked(checked);
|
setIsChecked(checked);
|
||||||
};
|
};
|
||||||
|
|
||||||
const form = useForm<AccountFormValues>({
|
const form = useForm<CreateSiteFormValues>({
|
||||||
resolver: zodResolver(accountFormSchema),
|
resolver: zodResolver(createSiteFormSchema),
|
||||||
defaultValues,
|
defaultValues
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -114,7 +125,7 @@ export default function CreateSiteForm({ open, setOpen }: CreateSiteFormProps) {
|
||||||
toast({
|
toast({
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
title: "Error picking site defaults",
|
title: "Error picking site defaults",
|
||||||
description: formatAxiosError(e),
|
description: formatAxiosError(e)
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
|
@ -125,7 +136,7 @@ export default function CreateSiteForm({ open, setOpen }: CreateSiteFormProps) {
|
||||||
}
|
}
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
async function onSubmit(data: AccountFormValues) {
|
async function onSubmit(data: CreateSiteFormValues) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
if (!siteDefaults || !keypair) {
|
if (!siteDefaults || !keypair) {
|
||||||
return;
|
return;
|
||||||
|
@ -135,29 +146,44 @@ export default function CreateSiteForm({ open, setOpen }: CreateSiteFormProps) {
|
||||||
subnet: siteDefaults.subnet,
|
subnet: siteDefaults.subnet,
|
||||||
exitNodeId: siteDefaults.exitNodeId,
|
exitNodeId: siteDefaults.exitNodeId,
|
||||||
pubKey: keypair.publicKey,
|
pubKey: keypair.publicKey,
|
||||||
type: data.method,
|
type: data.method
|
||||||
};
|
};
|
||||||
if (data.method === "newt") {
|
if (data.method === "newt") {
|
||||||
payload.secret = siteDefaults.newtSecret;
|
payload.secret = siteDefaults.newtSecret;
|
||||||
payload.newtId = siteDefaults.newtId;
|
payload.newtId = siteDefaults.newtId;
|
||||||
}
|
}
|
||||||
const res = await api
|
const res = await api
|
||||||
.put(`/org/${orgId}/site/`, payload)
|
.put<
|
||||||
|
AxiosResponse<CreateSiteResponse>
|
||||||
|
>(`/org/${orgId}/site/`, payload)
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
toast({
|
toast({
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
title: "Error creating site",
|
title: "Error creating site",
|
||||||
description: formatAxiosError(e),
|
description: formatAxiosError(e)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res && res.status === 201) {
|
if (res && res.status === 201) {
|
||||||
const niceId = res.data.data.niceId;
|
const niceId = res.data.data.niceId;
|
||||||
// navigate to the site page
|
// navigate to the site page
|
||||||
router.push(`/${orgId}/settings/sites/${niceId}`);
|
// router.push(`/${orgId}/settings/sites/${niceId}`);
|
||||||
|
|
||||||
// close the modal
|
// close the modal
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
|
|
||||||
|
const data = res.data.data;
|
||||||
|
|
||||||
|
onCreate?.({
|
||||||
|
name: data.name,
|
||||||
|
id: data.siteId,
|
||||||
|
nice: data.niceId.toString(),
|
||||||
|
mbIn: "0 MB",
|
||||||
|
mbOut: "0 MB",
|
||||||
|
orgId: orgId as string,
|
||||||
|
type: data.type as any,
|
||||||
|
online: false
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
@ -275,8 +301,8 @@ PersistentKeepalive = 5`
|
||||||
{form.watch("method") === "wireguard" &&
|
{form.watch("method") === "wireguard" &&
|
||||||
!isLoading ? (
|
!isLoading ? (
|
||||||
<CopyTextBox text={wgConfig} />
|
<CopyTextBox text={wgConfig} />
|
||||||
) : form.watch("method") === "wireguard" &&
|
) : form.watch("method") ===
|
||||||
isLoading ? (
|
"wireguard" && isLoading ? (
|
||||||
<p>
|
<p>
|
||||||
Loading WireGuard
|
Loading WireGuard
|
||||||
configuration...
|
configuration...
|
||||||
|
|
|
@ -6,10 +6,16 @@ import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger
|
||||||
} from "@app/components/ui/dropdown-menu";
|
} from "@app/components/ui/dropdown-menu";
|
||||||
import { Button } from "@app/components/ui/button";
|
import { Button } from "@app/components/ui/button";
|
||||||
import { ArrowRight, ArrowUpDown, Check, MoreHorizontal, X } from "lucide-react";
|
import {
|
||||||
|
ArrowRight,
|
||||||
|
ArrowUpDown,
|
||||||
|
Check,
|
||||||
|
MoreHorizontal,
|
||||||
|
X
|
||||||
|
} from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
|
@ -45,14 +51,10 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||||
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
||||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||||
const [selectedSite, setSelectedSite] = useState<SiteRow | null>(null);
|
const [selectedSite, setSelectedSite] = useState<SiteRow | null>(null);
|
||||||
|
const [rows, setRows] = useState<SiteRow[]>(sites);
|
||||||
|
|
||||||
const api = createApiClient(useEnvContext());
|
const api = createApiClient(useEnvContext());
|
||||||
|
|
||||||
const callApi = async () => {
|
|
||||||
const res = await api.put<AxiosResponse<any>>(`/newt`);
|
|
||||||
console.log(res);
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteSite = (siteId: number) => {
|
const deleteSite = (siteId: number) => {
|
||||||
api.delete(`/site/${siteId}`)
|
api.delete(`/site/${siteId}`)
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
|
@ -60,7 +62,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||||
toast({
|
toast({
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
title: "Error deleting site",
|
title: "Error deleting site",
|
||||||
description: formatAxiosError(e, "Error deleting site"),
|
description: formatAxiosError(e, "Error deleting site")
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
@ -84,7 +86,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "nice",
|
accessorKey: "nice",
|
||||||
|
@ -100,7 +102,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "mbIn",
|
accessorKey: "mbIn",
|
||||||
|
@ -116,7 +118,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "mbOut",
|
accessorKey: "mbOut",
|
||||||
|
@ -132,7 +134,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "type",
|
accessorKey: "type",
|
||||||
|
@ -167,7 +169,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "online",
|
accessorKey: "online",
|
||||||
|
@ -203,7 +205,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "actions",
|
id: "actions",
|
||||||
|
@ -229,16 +231,13 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||||
View settings
|
View settings
|
||||||
</Link>
|
</Link>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem>
|
<DropdownMenuItem
|
||||||
<button
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedSite(siteRow);
|
setSelectedSite(siteRow);
|
||||||
setIsDeleteModalOpen(true);
|
setIsDeleteModalOpen(true);
|
||||||
}}
|
}}
|
||||||
className="text-red-500"
|
|
||||||
>
|
>
|
||||||
Delete
|
<span className="text-red-500">Delete</span>
|
||||||
</button>
|
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
@ -252,8 +251,8 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -261,6 +260,9 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||||
<CreateSiteForm
|
<CreateSiteForm
|
||||||
open={isCreateModalOpen}
|
open={isCreateModalOpen}
|
||||||
setOpen={setIsCreateModalOpen}
|
setOpen={setIsCreateModalOpen}
|
||||||
|
onCreate={(val) => {
|
||||||
|
setRows([val, ...rows]);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{selectedSite && (
|
{selectedSite && (
|
||||||
|
@ -302,12 +304,11 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||||
|
|
||||||
<SitesDataTable
|
<SitesDataTable
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={sites}
|
data={rows}
|
||||||
addSite={() => {
|
addSite={() => {
|
||||||
setIsCreateModalOpen(true);
|
setIsCreateModalOpen(true);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/* <button onClick={callApi}>Create Newt</button> */}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { createApiClient } from "@app/api";
|
||||||
|
import { Button } from "@app/components/ui/button";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardFooter,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle
|
||||||
|
} from "@app/components/ui/card";
|
||||||
|
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||||
|
import { AuthWithAccessTokenResponse } from "@server/routers/resource";
|
||||||
|
import { AxiosResponse } from "axios";
|
||||||
|
import { Loader2 } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
type AccessTokenProps = {
|
||||||
|
accessTokenId: string | undefined;
|
||||||
|
accessToken: string | undefined;
|
||||||
|
resourceId: number;
|
||||||
|
redirectUrl: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function AccessToken({
|
||||||
|
accessTokenId,
|
||||||
|
accessToken,
|
||||||
|
resourceId,
|
||||||
|
redirectUrl
|
||||||
|
}: AccessTokenProps) {
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
const api = createApiClient(useEnvContext());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!accessTokenId || !accessToken) {
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function check() {
|
||||||
|
try {
|
||||||
|
const res = await api.post<
|
||||||
|
AxiosResponse<AuthWithAccessTokenResponse>
|
||||||
|
>(`/auth/resource/${resourceId}/access-token`, {
|
||||||
|
accessToken,
|
||||||
|
accessTokenId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.data.data.session) {
|
||||||
|
window.location.href = redirectUrl;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error checking access token", e);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
check();
|
||||||
|
}, [accessTokenId, accessToken]);
|
||||||
|
|
||||||
|
return loading ? (
|
||||||
|
<div></div>
|
||||||
|
) : (
|
||||||
|
<Card className="w-full max-w-md">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-center text-2xl font-bold">
|
||||||
|
Access URL Invalid
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
This shared access URL is invalid. Please contact the resource
|
||||||
|
owner for a new URL.
|
||||||
|
<div className="text-center mt-4">
|
||||||
|
<Button>
|
||||||
|
<Link href="/">Go Home</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,32 +0,0 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
import { Button } from "@app/components/ui/button";
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardFooter,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle
|
|
||||||
} from "@app/components/ui/card";
|
|
||||||
import Link from "next/link";
|
|
||||||
|
|
||||||
export default function AccessTokenInvalid() {
|
|
||||||
return (
|
|
||||||
<Card className="w-full max-w-md">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="text-center text-2xl font-bold">
|
|
||||||
Acess URL Invalid
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
This shared access URL is invalid. Please contact the resource
|
|
||||||
owner for a new URL.
|
|
||||||
<div className="text-center mt-4">
|
|
||||||
<Button>
|
|
||||||
<Link href="/">Go Home</Link>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -14,7 +14,8 @@ import ResourceNotFound from "./components/ResourceNotFound";
|
||||||
import ResourceAccessDenied from "./components/ResourceAccessDenied";
|
import ResourceAccessDenied from "./components/ResourceAccessDenied";
|
||||||
import { cookies } from "next/headers";
|
import { cookies } from "next/headers";
|
||||||
import { CheckResourceSessionResponse } from "@server/routers/auth";
|
import { CheckResourceSessionResponse } from "@server/routers/auth";
|
||||||
import AccessTokenInvalid from "./components/AccessTokenInvalid";
|
import AccessTokenInvalid from "./components/AccessToken";
|
||||||
|
import AccessToken from "./components/AccessToken";
|
||||||
|
|
||||||
export default async function ResourceAuthPage(props: {
|
export default async function ResourceAuthPage(props: {
|
||||||
params: Promise<{ resourceId: number }>;
|
params: Promise<{ resourceId: number }>;
|
||||||
|
@ -50,35 +51,6 @@ export default async function ResourceAuthPage(props: {
|
||||||
|
|
||||||
const redirectUrl = searchParams.redirect || authInfo.url;
|
const redirectUrl = searchParams.redirect || authInfo.url;
|
||||||
|
|
||||||
if (searchParams.token) {
|
|
||||||
let doRedirect = false;
|
|
||||||
try {
|
|
||||||
const res = await internal.post<
|
|
||||||
AxiosResponse<AuthWithAccessTokenResponse>
|
|
||||||
>(
|
|
||||||
`/auth/resource/${params.resourceId}/access-token`,
|
|
||||||
{
|
|
||||||
accessToken: searchParams.token
|
|
||||||
},
|
|
||||||
await authCookieHeader()
|
|
||||||
);
|
|
||||||
|
|
||||||
if (res.data.data.session) {
|
|
||||||
doRedirect = true;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return (
|
|
||||||
<div className="w-full max-w-md">
|
|
||||||
<AccessTokenInvalid />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (doRedirect) {
|
|
||||||
redirect(redirectUrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasAuth =
|
const hasAuth =
|
||||||
authInfo.password ||
|
authInfo.password ||
|
||||||
authInfo.pincode ||
|
authInfo.pincode ||
|
||||||
|
@ -146,6 +118,20 @@ export default async function ResourceAuthPage(props: {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (searchParams.token) {
|
||||||
|
const [accessTokenId, accessToken] = searchParams.token.split(".");
|
||||||
|
return (
|
||||||
|
<div className="w-full max-w-md">
|
||||||
|
<AccessToken
|
||||||
|
accessToken={accessToken}
|
||||||
|
accessTokenId={accessTokenId}
|
||||||
|
resourceId={params.resourceId}
|
||||||
|
redirectUrl={redirectUrl}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{userIsUnauthorized && isSSOOnly ? (
|
{userIsUnauthorized && isSSOOnly ? (
|
||||||
|
|
Loading…
Add table
Reference in a new issue