a lot of things
This commit is contained in:
@@ -5,6 +5,7 @@ import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { authMiddleware } from "../middleware/authorization";
|
||||
import { z } from "zod";
|
||||
import { ValueType } from "../../modules/credentials/credentialTypeDefs";
|
||||
|
||||
/* /v1/credential-type */
|
||||
export default createRoute(
|
||||
@@ -24,13 +25,15 @@ export default createRoute(
|
||||
name: z.string(),
|
||||
required: z.boolean(),
|
||||
secure: z.boolean(),
|
||||
valueType: z.enum(["plain_text", "password"]),
|
||||
valueType: z.enum(Object.values(ValueType)),
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
const data = schema.parse(body);
|
||||
|
||||
console.log("Creating Credential Type with data:", data);
|
||||
|
||||
const credentialType = await credentialTypes.create(data as any);
|
||||
|
||||
const response = apiResponse.created(
|
||||
|
||||
@@ -5,6 +5,7 @@ import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { authMiddleware } from "../middleware/authorization";
|
||||
import { z } from "zod";
|
||||
import { ValueType } from "../../modules/credentials/credentialTypeDefs";
|
||||
|
||||
/* /v1/credential-type/:id */
|
||||
export default createRoute(
|
||||
@@ -26,7 +27,7 @@ export default createRoute(
|
||||
name: z.string(),
|
||||
required: z.boolean(),
|
||||
secure: z.boolean(),
|
||||
valueType: z.enum(["plain_text", "password"]),
|
||||
valueType: z.enum(Object.values(ValueType)),
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
|
||||
@@ -16,6 +16,7 @@ export default createRoute(
|
||||
|
||||
const schema = z.object({
|
||||
name: z.string().min(1, "Name is required"),
|
||||
notes: z.string().optional(),
|
||||
typeId: z.string().min(1, "Type ID is required"),
|
||||
companyId: z.string().min(1, "Company ID is required"),
|
||||
fields: z.array(
|
||||
|
||||
@@ -5,9 +5,12 @@ import { default as update } from "./update";
|
||||
import { default as updateFields } from "./updateFields";
|
||||
import { default as fetchFields } from "./fetchFields";
|
||||
import { default as readSecureValues } from "./readSecureValues";
|
||||
import { default as readSecureValue } from "./readSecureValue";
|
||||
import { default as deleteCredential } from "./delete";
|
||||
import { default as valueTypes } from "./valueTypes";
|
||||
|
||||
export {
|
||||
valueTypes,
|
||||
fetch,
|
||||
fetchByCompany,
|
||||
create,
|
||||
@@ -15,5 +18,6 @@ export {
|
||||
updateFields,
|
||||
fetchFields,
|
||||
readSecureValues,
|
||||
readSecureValue,
|
||||
deleteCredential as delete,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Hono } from "hono/tiny";
|
||||
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||
import { credentials } from "../../managers/credentials";
|
||||
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { authMiddleware } from "../middleware/authorization";
|
||||
|
||||
/* GET /v1/credential/credentials/:id/secure-values/:fieldId */
|
||||
export default createRoute(
|
||||
"get",
|
||||
["/credentials/:id/secure-values/:fieldId"],
|
||||
|
||||
async (c) => {
|
||||
const credential = await credentials.fetch(c.req.param("id"));
|
||||
const fieldId = c.req.param("fieldId");
|
||||
const value = await credential.readSecureFieldValue(fieldId);
|
||||
|
||||
const response = apiResponse.successful(
|
||||
"Secure Value Fetched Successfully!",
|
||||
{ fieldId, value },
|
||||
);
|
||||
return c.json(response, response.status as ContentfulStatusCode);
|
||||
},
|
||||
authMiddleware({
|
||||
permissions: ["credential.fetch", "credential.secure_values.read"],
|
||||
}),
|
||||
);
|
||||
@@ -17,11 +17,29 @@ export default createRoute(
|
||||
|
||||
const schema = z.object({
|
||||
name: z.string().optional(),
|
||||
notes: z.string().nullable().optional(),
|
||||
fields: z
|
||||
.array(
|
||||
z.object({
|
||||
fieldId: z.string(),
|
||||
value: z.string(),
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
});
|
||||
|
||||
const data = schema.parse(body);
|
||||
|
||||
await credential.update(data);
|
||||
if (data.fields) {
|
||||
await credential.validateAndUpdateFields(data.fields);
|
||||
}
|
||||
|
||||
if (data.name !== undefined || data.notes !== undefined) {
|
||||
await credential.update({
|
||||
...(data.name !== undefined && { name: data.name }),
|
||||
...(data.notes !== undefined && { notes: data.notes }),
|
||||
});
|
||||
}
|
||||
|
||||
const response = apiResponse.successful(
|
||||
"Credential Updated Successfully!",
|
||||
|
||||
@@ -18,7 +18,6 @@ export default createRoute(
|
||||
const schema = z.object({
|
||||
fields: z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
fieldId: z.string(),
|
||||
value: z.string(),
|
||||
}),
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { ValueType } from "../../modules/credentials/credentialTypeDefs";
|
||||
import { authMiddleware } from "../middleware/authorization";
|
||||
|
||||
/* GET /v1/credential/valuetypes */
|
||||
export default createRoute(
|
||||
"get",
|
||||
["/valuetypes"],
|
||||
|
||||
async (c) => {
|
||||
const valueTypes = Object.values(ValueType);
|
||||
|
||||
const response = apiResponse.successful(
|
||||
"Value Types Fetched Successfully!",
|
||||
valueTypes,
|
||||
);
|
||||
return c.json(response, response.status as ContentfulStatusCode);
|
||||
},
|
||||
authMiddleware(),
|
||||
);
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Hono } from "hono";
|
||||
import * as userRoutes from "../user/@me";
|
||||
import * as meRoutes from "../user/@me";
|
||||
import * as userRoutes from "../user";
|
||||
|
||||
const authRouter = new Hono();
|
||||
Object.values(meRoutes).map((r) => authRouter.route("/", r));
|
||||
Object.values(userRoutes).map((r) => authRouter.route("/", r));
|
||||
|
||||
export default authRouter;
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { z } from "zod";
|
||||
import { apiResponse } from "../../../modules/api-utils/apiResponse";
|
||||
import { createRoute } from "../../../modules/api-utils/createRoute";
|
||||
import { authMiddleware } from "../../middleware/authorization";
|
||||
|
||||
const updateSchema = z
|
||||
.object({
|
||||
name: z.string().optional(),
|
||||
image: z.string().optional(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export default createRoute(
|
||||
"patch",
|
||||
["/@me"],
|
||||
async (c) => {
|
||||
const updatedUser = await c.get("user")?.update(await c.req.json());
|
||||
const body = updateSchema.parse(await c.req.json());
|
||||
const updatedUser = await c.get("user")?.update(body);
|
||||
const response = apiResponse.successful(
|
||||
"Successfully updated user.",
|
||||
updatedUser?.toJson(),
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { z } from "zod";
|
||||
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||
import { authMiddleware } from "../middleware/authorization";
|
||||
import { users } from "../../managers/users";
|
||||
import GenericError from "../../Errors/GenericError";
|
||||
|
||||
const checkPermissionSchema = z.object({
|
||||
permissions: z
|
||||
.array(z.string().min(1, "Permission node cannot be empty"))
|
||||
.min(1, "At least one permission is required"),
|
||||
});
|
||||
|
||||
/* POST /v1/user/users/:identifier/check-permission */
|
||||
export default createRoute(
|
||||
"post",
|
||||
["/users/:identifier/check-permission"],
|
||||
|
||||
async (c) => {
|
||||
const identifier = c.req.param("identifier");
|
||||
|
||||
const user = await users.fetchUser({ id: identifier });
|
||||
if (!user)
|
||||
throw new GenericError({
|
||||
name: "UserNotFound",
|
||||
message: `User with identifier '${identifier}' was not found.`,
|
||||
status: 404,
|
||||
});
|
||||
|
||||
const body = await c.req.json();
|
||||
const { permissions } = checkPermissionSchema.parse(body);
|
||||
|
||||
const results = await Promise.all(
|
||||
permissions.map(async (permission) => ({
|
||||
permission,
|
||||
hasPermission: await user.hasPermission(permission),
|
||||
})),
|
||||
);
|
||||
|
||||
const response = apiResponse.successful("Permission check completed.", {
|
||||
results,
|
||||
});
|
||||
return c.json(response, response.status as ContentfulStatusCode);
|
||||
},
|
||||
authMiddleware({ permissions: ["user.read.other"] }),
|
||||
);
|
||||
@@ -0,0 +1,34 @@
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||
import { authMiddleware } from "../middleware/authorization";
|
||||
import { users } from "../../managers/users";
|
||||
import GenericError from "../../Errors/GenericError";
|
||||
|
||||
/* DELETE /v1/user/users/:identifier */
|
||||
export default createRoute(
|
||||
"delete",
|
||||
["/users/:identifier"],
|
||||
|
||||
async (c) => {
|
||||
const identifier = c.req.param("identifier");
|
||||
|
||||
const user = await users.fetchUser({ id: identifier });
|
||||
if (!user)
|
||||
throw new GenericError({
|
||||
name: "UserNotFound",
|
||||
message: `User with identifier '${identifier}' was not found.`,
|
||||
status: 404,
|
||||
});
|
||||
|
||||
const userData = user.toJson();
|
||||
await users.deleteUser(user.id);
|
||||
|
||||
const response = apiResponse.successful(
|
||||
"User Deleted Successfully!",
|
||||
userData,
|
||||
);
|
||||
return c.json(response, response.status as ContentfulStatusCode);
|
||||
},
|
||||
authMiddleware({ permissions: ["user.delete.other"] }),
|
||||
);
|
||||
@@ -0,0 +1,31 @@
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||
import { authMiddleware } from "../middleware/authorization";
|
||||
import { users } from "../../managers/users";
|
||||
import GenericError from "../../Errors/GenericError";
|
||||
|
||||
/* GET /v1/user/users/:identifier */
|
||||
export default createRoute(
|
||||
"get",
|
||||
["/users/:identifier"],
|
||||
|
||||
async (c) => {
|
||||
const identifier = c.req.param("identifier");
|
||||
|
||||
const user = await users.fetchUser({ id: identifier });
|
||||
if (!user)
|
||||
throw new GenericError({
|
||||
name: "UserNotFound",
|
||||
message: `User with identifier '${identifier}' was not found.`,
|
||||
status: 404,
|
||||
});
|
||||
|
||||
const response = apiResponse.successful(
|
||||
"User Fetched Successfully!",
|
||||
user.toJson(),
|
||||
);
|
||||
return c.json(response, response.status as ContentfulStatusCode);
|
||||
},
|
||||
authMiddleware({ permissions: ["user.read.other"] }),
|
||||
);
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||
import { authMiddleware } from "../middleware/authorization";
|
||||
import { users } from "../../managers/users";
|
||||
|
||||
/* GET /v1/user/users */
|
||||
export default createRoute(
|
||||
"get",
|
||||
["/users"],
|
||||
|
||||
async (c) => {
|
||||
const allUsers = await users.fetchAllUsers();
|
||||
const usersArray = allUsers.map((u) => u.toJson());
|
||||
|
||||
const response = apiResponse.successful(
|
||||
"Users Fetched Successfully!",
|
||||
usersArray,
|
||||
);
|
||||
return c.json(response, response.status as ContentfulStatusCode);
|
||||
},
|
||||
authMiddleware({ permissions: ["user.read.other", "user.list.other"] }),
|
||||
);
|
||||
@@ -0,0 +1,34 @@
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||
import { authMiddleware } from "../middleware/authorization";
|
||||
import { users } from "../../managers/users";
|
||||
import GenericError from "../../Errors/GenericError";
|
||||
|
||||
/* GET /v1/user/users/:identifier/roles */
|
||||
export default createRoute(
|
||||
"get",
|
||||
["/users/:identifier/roles"],
|
||||
|
||||
async (c) => {
|
||||
const identifier = c.req.param("identifier");
|
||||
|
||||
const user = await users.fetchUser({ id: identifier });
|
||||
if (!user)
|
||||
throw new GenericError({
|
||||
name: "UserNotFound",
|
||||
message: `User with identifier '${identifier}' was not found.`,
|
||||
status: 404,
|
||||
});
|
||||
|
||||
const roles = await user.fetchRoles();
|
||||
const rolesArray = roles.map((r) => r.toJson({ viewPermissions: true }));
|
||||
|
||||
const response = apiResponse.successful(
|
||||
"User Roles Fetched Successfully!",
|
||||
rolesArray,
|
||||
);
|
||||
return c.json(response, response.status as ContentfulStatusCode);
|
||||
},
|
||||
authMiddleware({ permissions: ["user.read.other", "role.read"] }),
|
||||
);
|
||||
@@ -0,0 +1,6 @@
|
||||
export { default as fetch } from "./fetch";
|
||||
export { default as fetchAll } from "./fetchAll";
|
||||
export { default as update } from "./update";
|
||||
export { default as deleteUser } from "./delete";
|
||||
export { default as fetchRoles } from "./fetchRoles";
|
||||
export { default as checkPermission } from "./checkPermission";
|
||||
@@ -0,0 +1,68 @@
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { z } from "zod";
|
||||
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||
import { authMiddleware } from "../middleware/authorization";
|
||||
import { users } from "../../managers/users";
|
||||
import GenericError from "../../Errors/GenericError";
|
||||
|
||||
const updateSchema = z
|
||||
.object({
|
||||
name: z.string().optional(),
|
||||
image: z.string().optional(),
|
||||
roles: z.array(z.string()).optional(),
|
||||
permissions: z.array(z.string()).optional(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
/* PATCH /v1/user/users/:identifier */
|
||||
export default createRoute(
|
||||
"patch",
|
||||
["/users/:identifier"],
|
||||
|
||||
async (c) => {
|
||||
const identifier = c.req.param("identifier");
|
||||
const requestingUser = c.get("user");
|
||||
|
||||
const user = await users.fetchUser({ id: identifier });
|
||||
if (!user)
|
||||
throw new GenericError({
|
||||
name: "UserNotFound",
|
||||
message: `User with identifier '${identifier}' was not found.`,
|
||||
status: 404,
|
||||
});
|
||||
|
||||
const body = updateSchema.parse(await c.req.json());
|
||||
|
||||
if (body.roles && !(await requestingUser.hasPermission("user.roles.other")))
|
||||
throw new GenericError({
|
||||
name: "InsufficientPermission",
|
||||
message: "You do not have permission to modify roles on another user.",
|
||||
status: 403,
|
||||
});
|
||||
|
||||
if (
|
||||
body.permissions &&
|
||||
!(await requestingUser.hasPermission("user.permissions.other"))
|
||||
)
|
||||
throw new GenericError({
|
||||
name: "InsufficientPermission",
|
||||
message:
|
||||
"You do not have permission to modify permissions on another user.",
|
||||
status: 403,
|
||||
});
|
||||
|
||||
const { roles: roleIds, permissions, ...profileData } = body;
|
||||
|
||||
if (Object.keys(profileData).length > 0) await user.update(profileData);
|
||||
if (roleIds) await user.setRoles(roleIds);
|
||||
if (permissions) await user.setPermissions(permissions);
|
||||
|
||||
const response = apiResponse.successful(
|
||||
"User Updated Successfully!",
|
||||
user.toJson(),
|
||||
);
|
||||
return c.json(response, response.status as ContentfulStatusCode);
|
||||
},
|
||||
authMiddleware({ permissions: ["user.write.other"] }),
|
||||
);
|
||||
@@ -80,7 +80,9 @@ export class CompanyController {
|
||||
city: this.cw_Data?.city,
|
||||
state: this.cw_Data?.state,
|
||||
zip: this.cw_Data?.zip,
|
||||
country: this.cw_Data?.country.name,
|
||||
country: this.cw_Data?.country
|
||||
? this.cw_Data.country.name
|
||||
: "United States",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ import { fieldValidator } from "../modules/credentials/fieldValidator";
|
||||
import {
|
||||
CredentialField,
|
||||
CredentialTypeField,
|
||||
ValueType,
|
||||
} from "../modules/credentials/credentialTypeDefs";
|
||||
import { generateSecureValue } from "../modules/credentials/generateSecureValue";
|
||||
import { readSecureValue } from "../modules/credentials/readSecureValue";
|
||||
@@ -24,6 +25,7 @@ import GenericError from "../Errors/GenericError";
|
||||
export class CredentialController {
|
||||
public readonly id: string;
|
||||
public name: string;
|
||||
public notes: string | null;
|
||||
public readonly typeId: string;
|
||||
public readonly companyId: string;
|
||||
public fields: any;
|
||||
@@ -44,6 +46,7 @@ export class CredentialController {
|
||||
) {
|
||||
this.id = credentialData.id;
|
||||
this.name = credentialData.name;
|
||||
this.notes = credentialData.notes;
|
||||
this.typeId = credentialData.typeId;
|
||||
this.companyId = credentialData.companyId;
|
||||
this._type = credentialData.type;
|
||||
@@ -52,14 +55,21 @@ export class CredentialController {
|
||||
this.fields = (() => {
|
||||
let fields = credentialData.fields as Record<string, any>;
|
||||
|
||||
this._secureValues.forEach((sv) => (fields[sv.name] = `secure-${sv.id}`));
|
||||
return (this._type.fields! as any).map((f: any) => ({
|
||||
id: f.id,
|
||||
name: f.name,
|
||||
secure: f.secure,
|
||||
required: f.required,
|
||||
valueType: f.valueType as ValueType,
|
||||
value: f.secure
|
||||
? `secure-${this._secureValues.find((sv) => sv.name === f.id)?.id}`
|
||||
: fields[f.id],
|
||||
}));
|
||||
|
||||
return fields;
|
||||
})();
|
||||
this.createdAt = credentialData.createdAt;
|
||||
this.updatedAt = credentialData.updatedAt;
|
||||
|
||||
console.log(credentialData);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,6 +82,7 @@ export class CredentialController {
|
||||
*/
|
||||
private _updateInternalValues(credentialData: Credential) {
|
||||
this.name = credentialData.name;
|
||||
this.notes = credentialData.notes;
|
||||
this.fields = credentialData.fields;
|
||||
this.updatedAt = credentialData.updatedAt;
|
||||
}
|
||||
@@ -174,7 +185,6 @@ export class CredentialController {
|
||||
const nonSecureFields = this.fields as Record<string, any>;
|
||||
Object.entries(nonSecureFields || {}).forEach(([fieldId, value]) => {
|
||||
fields.push({
|
||||
id: `${this.id}-${fieldId}`, // Generate a consistent ID
|
||||
fieldId,
|
||||
value: value as string,
|
||||
});
|
||||
@@ -183,7 +193,6 @@ export class CredentialController {
|
||||
// Add secure fields from SecureValue table (encrypted)
|
||||
this._secureValues.forEach((secureValue) => {
|
||||
fields.push({
|
||||
id: secureValue.id,
|
||||
fieldId: secureValue.name,
|
||||
value: secureValue.content, // Encrypted value
|
||||
});
|
||||
@@ -253,11 +262,12 @@ export class CredentialController {
|
||||
* @returns {Promise<CredentialController>} - The updated credential controller
|
||||
*/
|
||||
async update(
|
||||
data: Partial<Pick<Credential, "name">>,
|
||||
data: Partial<Pick<Credential, "name" | "notes">>,
|
||||
): Promise<CredentialController> {
|
||||
const pData = z
|
||||
.object({
|
||||
name: z.string().optional(),
|
||||
notes: z.string().nullable().optional(),
|
||||
})
|
||||
.strict()
|
||||
.parse(data);
|
||||
@@ -307,6 +317,7 @@ export class CredentialController {
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
notes: this.notes,
|
||||
typeId: this.typeId,
|
||||
companyId: this.companyId,
|
||||
fields: this.fields,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Collection } from "@discordjs/collection";
|
||||
import { z } from "zod";
|
||||
import { Role } from "../../generated/prisma/client";
|
||||
import { User } from "../../generated/prisma/browser";
|
||||
import { SessionTokensObject } from "./SessionController";
|
||||
@@ -9,6 +8,10 @@ import { prisma } from "../constants";
|
||||
import { events } from "../modules/globalEvents";
|
||||
import { RoleController } from "./RoleController";
|
||||
import { roles } from "../managers/roles";
|
||||
import { signPermissions } from "../modules/permission-utils/signPermissions";
|
||||
import { DecodedPermissionsBlock } from "../types/PermissionTypes";
|
||||
import jwt from "jsonwebtoken";
|
||||
import { permissionsPrivateKey } from "../constants";
|
||||
|
||||
export default class UserController {
|
||||
public id: string;
|
||||
@@ -18,6 +21,7 @@ export default class UserController {
|
||||
public image: string | null;
|
||||
|
||||
private _roles: Collection<string, Role>;
|
||||
private _permissions: string | null;
|
||||
|
||||
public createdAt: Date;
|
||||
public updatedAt: Date;
|
||||
@@ -29,6 +33,7 @@ export default class UserController {
|
||||
this.image = userdata.image;
|
||||
this.updatedAt = userdata.updatedAt;
|
||||
this.createdAt = userdata.createdAt;
|
||||
this._permissions = userdata.permissions ?? null;
|
||||
|
||||
this._roles = (() => {
|
||||
let collection = new Collection<string, Role>();
|
||||
@@ -77,22 +82,13 @@ export default class UserController {
|
||||
* @param data - A partial of the user data
|
||||
* @returns {Promise<UserController>} - The updated user controller
|
||||
*/
|
||||
public async update(data: Partial<User>) {
|
||||
// Parsed Data With Schema
|
||||
const pData = z
|
||||
.object({
|
||||
name: z.string().optional(),
|
||||
image: z.string().optional(),
|
||||
})
|
||||
.strict()
|
||||
.parse(data);
|
||||
|
||||
public async update(data: Partial<Pick<User, "name" | "image">>) {
|
||||
if (Object.keys(data).length == 0)
|
||||
throw new BodyError("Body cannot be empty.");
|
||||
|
||||
const updatedUser = await prisma.user.update({
|
||||
where: { id: this.id },
|
||||
data: pData,
|
||||
data,
|
||||
});
|
||||
|
||||
this._updateInternalValues(updatedUser);
|
||||
@@ -101,6 +97,87 @@ export default class UserController {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Roles
|
||||
*
|
||||
* Replace the user's roles with the provided array of role identifiers (id or moniker).
|
||||
* Validates that each role exists before assigning.
|
||||
*
|
||||
* @param roleIdentifiers - Array of role ids or monikers to assign
|
||||
* @returns {Promise<UserController>} - The updated user controller
|
||||
*/
|
||||
public async setRoles(roleIdentifiers: string[]): Promise<UserController> {
|
||||
const resolvedRoles = await Promise.all(
|
||||
roleIdentifiers.map((identifier) => roles.fetch(identifier)),
|
||||
);
|
||||
|
||||
const updatedUser = await prisma.user.update({
|
||||
where: { id: this.id },
|
||||
data: {
|
||||
roles: {
|
||||
set: resolvedRoles.map((r) => ({ id: r.id })),
|
||||
},
|
||||
},
|
||||
include: { roles: true },
|
||||
});
|
||||
|
||||
this._updateInternalValues(updatedUser);
|
||||
this._roles = new Collection<string, Role>();
|
||||
updatedUser.roles.map((v: any) => this._roles.set(v.id, v));
|
||||
|
||||
for (const role of resolvedRoles) {
|
||||
events.emit("user:role:assigned", { user: this, role });
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Permissions
|
||||
*
|
||||
* Replace the user's direct permissions with the provided array of permission strings.
|
||||
* Signs the permissions with the user issuer before storing.
|
||||
*
|
||||
* @param permissions - Array of permission node strings to assign
|
||||
* @returns {Promise<UserController>} - The updated user controller
|
||||
*/
|
||||
public async setPermissions(permissions: string[]): Promise<UserController> {
|
||||
const signed = signPermissions({
|
||||
issuer: "user",
|
||||
subject: this.id,
|
||||
permissions,
|
||||
});
|
||||
|
||||
const updatedUser = await prisma.user.update({
|
||||
where: { id: this.id },
|
||||
data: { permissions: signed },
|
||||
});
|
||||
|
||||
this._updateInternalValues(updatedUser);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read Permissions
|
||||
*
|
||||
* Verifies and decodes the user's direct permissions JWT and returns the array of
|
||||
* permission node strings. Returns an empty array if the user has no direct permissions.
|
||||
*
|
||||
* @returns {string[]} The user's direct permission nodes
|
||||
*/
|
||||
public readPermissions(): string[] {
|
||||
if (!this._permissions) return [];
|
||||
|
||||
const decoded = jwt.verify(this._permissions, permissionsPrivateKey, {
|
||||
algorithms: ["RS256"],
|
||||
issuer: "user",
|
||||
subject: this.id,
|
||||
}) as DecodedPermissionsBlock;
|
||||
|
||||
return decoded.permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch Roles
|
||||
*
|
||||
@@ -185,9 +262,12 @@ export default class UserController {
|
||||
: this._roles.size > 0
|
||||
? this._roles.map((v) => v.moniker)
|
||||
: undefined,
|
||||
permissions: opts?.safeReturn ? undefined : this.readPermissions(),
|
||||
login: opts?.safeReturn ? undefined : this.login,
|
||||
email: opts?.safeReturn ? undefined : this.email,
|
||||
image: this.image,
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +79,8 @@ export const credentialTypes = {
|
||||
});
|
||||
}
|
||||
|
||||
console.log(data.fields);
|
||||
|
||||
const credentialType = await prisma.credentialType.create({
|
||||
data: {
|
||||
name: data.name,
|
||||
@@ -91,6 +93,8 @@ export const credentialTypes = {
|
||||
},
|
||||
});
|
||||
|
||||
console.log(credentialType.fields);
|
||||
|
||||
return new CredentialTypeController(credentialType);
|
||||
},
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ export const credentials = {
|
||||
*/
|
||||
async create(data: {
|
||||
name: string;
|
||||
notes?: string;
|
||||
typeId: string;
|
||||
companyId: string;
|
||||
fields: {
|
||||
@@ -131,6 +132,7 @@ export const credentials = {
|
||||
const credential = await prisma.credential.create({
|
||||
data: {
|
||||
name: data.name,
|
||||
notes: data.notes,
|
||||
typeId: data.typeId,
|
||||
companyId: data.companyId,
|
||||
fields: fieldsObject,
|
||||
|
||||
+27
-4
@@ -106,8 +106,31 @@ export const users = {
|
||||
|
||||
return controller;
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @TODO Figure out default permissions
|
||||
*/
|
||||
/**
|
||||
* Fetch all users
|
||||
*
|
||||
* Returns an array of UserController instances for every user in the database.
|
||||
*
|
||||
* @returns {Promise<UserController[]>} Array of user controllers
|
||||
*/
|
||||
async fetchAllUsers(): Promise<UserController[]> {
|
||||
const allUsers = await prisma.user.findMany({
|
||||
include: { roles: true },
|
||||
});
|
||||
|
||||
return allUsers.map((u) => new UserController(u));
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete a user
|
||||
*
|
||||
* Removes a user record from the database by their id.
|
||||
*
|
||||
* @param userId - The id of the user to delete
|
||||
*/
|
||||
async deleteUser(userId: string): Promise<void> {
|
||||
await prisma.user.delete({ where: { id: userId } });
|
||||
events.emit("user:deleted", { id: userId });
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
export enum ValueType {
|
||||
PLAIN_TEXT = "plain_text",
|
||||
LICENSE_KEY = "license_key",
|
||||
IP_ADDRESS = "ip_address",
|
||||
GENERIC_SECRET = "generic_secret",
|
||||
BITLOCKER_KEY = "bitlocker_key",
|
||||
PASSWORD = "password",
|
||||
}
|
||||
|
||||
@@ -12,7 +16,6 @@ export interface CredentialTypeField {
|
||||
}
|
||||
|
||||
export interface CredentialField {
|
||||
id: string; // CUID
|
||||
fieldId: string; // I.e. "clientId", "clientSecret", etc.
|
||||
value: string; // Encrypted value stored in the database
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ export const fieldValidator = async (
|
||||
acceptableFields: CredentialTypeField[],
|
||||
): Promise<
|
||||
{
|
||||
id: string;
|
||||
fieldId: string;
|
||||
value: string;
|
||||
secure: boolean;
|
||||
@@ -47,7 +46,6 @@ export const fieldValidator = async (
|
||||
const matchingField = afCollection.get(field.fieldId)!;
|
||||
|
||||
return {
|
||||
id: field.id,
|
||||
fieldId: field.fieldId,
|
||||
value: field.value,
|
||||
secure: matchingField.secure,
|
||||
|
||||
@@ -6,10 +6,13 @@ export const generateSecureValue = (content: string) => {
|
||||
// Generate a hash of the content
|
||||
const hash = Password.hash(content);
|
||||
|
||||
// Parse the PKCS#1 PEM key into a proper KeyObject
|
||||
const publicKey = crypto.createPublicKey(secureValuesPublicKey);
|
||||
|
||||
// Encrypt the content using the .secureValues.pub public key
|
||||
const encrypted = crypto.publicEncrypt(
|
||||
{
|
||||
key: secureValuesPublicKey,
|
||||
key: publicKey,
|
||||
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
|
||||
oaepHash: "sha256",
|
||||
},
|
||||
|
||||
@@ -1,20 +1,36 @@
|
||||
import Password from "../tools/Password";
|
||||
import crypto from "crypto";
|
||||
import { secureValuesPrivateKey } from "../../constants";
|
||||
import GenericError from "../../Errors/GenericError";
|
||||
|
||||
const privateKey = crypto.createPrivateKey(secureValuesPrivateKey);
|
||||
|
||||
export const readSecureValue = (
|
||||
encryptedContent: string,
|
||||
hash?: string,
|
||||
): string => {
|
||||
// Decrypt the content using the .secureValues.key private key
|
||||
const decrypted = crypto.privateDecrypt(
|
||||
{
|
||||
key: secureValuesPrivateKey,
|
||||
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
|
||||
oaepHash: "sha256",
|
||||
},
|
||||
Buffer.from(encryptedContent, "base64"),
|
||||
);
|
||||
let decrypted: Buffer;
|
||||
|
||||
try {
|
||||
// Decrypt the content using the .secureValues.key private key
|
||||
decrypted = crypto.privateDecrypt(
|
||||
{
|
||||
key: privateKey,
|
||||
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
|
||||
oaepHash: "sha256",
|
||||
},
|
||||
Buffer.from(encryptedContent, "base64"),
|
||||
);
|
||||
} catch {
|
||||
throw new GenericError({
|
||||
name: "SecureValueDecryptionError",
|
||||
message:
|
||||
"Unable to decrypt secure value. The value was encrypted with a different key and must be re-entered.",
|
||||
cause:
|
||||
"RSA key mismatch — the current private key does not match the public key used to encrypt this value.",
|
||||
status: 422,
|
||||
});
|
||||
}
|
||||
|
||||
const content = decrypted.toString("utf-8");
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ interface EventTypes {
|
||||
user: UserController;
|
||||
updatedValues: Partial<User>;
|
||||
}) => void;
|
||||
"user:deleted": (data: { id: string }) => void;
|
||||
"user:authenticated": (data: {
|
||||
user: UserController;
|
||||
tokens: SessionTokensObject;
|
||||
|
||||
@@ -114,7 +114,10 @@ export const PERMISSION_NODES = {
|
||||
{
|
||||
node: "credential.secure_values.read",
|
||||
description: "Read secure values of a credential",
|
||||
usedIn: ["src/api/credentials/readSecureValues.ts"],
|
||||
usedIn: [
|
||||
"src/api/credentials/readSecureValues.ts",
|
||||
"src/api/credentials/readSecureValue.ts",
|
||||
],
|
||||
dependencies: ["credential.fetch"],
|
||||
},
|
||||
],
|
||||
@@ -231,6 +234,43 @@ export const PERMISSION_NODES = {
|
||||
description: "Update user information",
|
||||
usedIn: ["src/api/user/@me/update.ts"],
|
||||
},
|
||||
{
|
||||
node: "user.read.other",
|
||||
description: "Read other users' information",
|
||||
usedIn: [
|
||||
"src/api/user/fetch.ts",
|
||||
"src/api/user/fetchRoles.ts",
|
||||
"src/api/user/checkPermission.ts",
|
||||
],
|
||||
},
|
||||
{
|
||||
node: "user.list.other",
|
||||
description: "List all users",
|
||||
usedIn: ["src/api/user/fetchAll.ts"],
|
||||
dependencies: ["user.read.other"],
|
||||
},
|
||||
{
|
||||
node: "user.write.other",
|
||||
description: "Update other users' information",
|
||||
usedIn: ["src/api/user/update.ts"],
|
||||
},
|
||||
{
|
||||
node: "user.roles.other",
|
||||
description: "Modify roles assigned to other users",
|
||||
usedIn: ["src/api/user/update.ts"],
|
||||
dependencies: ["user.write.other"],
|
||||
},
|
||||
{
|
||||
node: "user.permissions.other",
|
||||
description: "Modify direct permissions assigned to other users",
|
||||
usedIn: ["src/api/user/update.ts"],
|
||||
dependencies: ["user.write.other"],
|
||||
},
|
||||
{
|
||||
node: "user.delete.other",
|
||||
description: "Delete other users",
|
||||
usedIn: ["src/api/user/delete.ts"],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user