a lot of things

This commit is contained in:
2026-02-20 11:46:30 -06:00
parent 987a1c8a6a
commit 70284bc14e
37 changed files with 1080 additions and 79 deletions
+4 -1
View File
@@ -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(
+2 -1
View File
@@ -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(),
+1
View File
@@ -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(
+4
View File
@@ -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,
};
+27
View File
@@ -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"],
}),
);
+19 -1
View File
@@ -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!",
-1
View File
@@ -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(),
}),
+22
View File
@@ -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(),
);
+3 -1
View File
@@ -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;
+10 -1
View File
@@ -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(),
+47
View File
@@ -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"] }),
);
+34
View File
@@ -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"] }),
);
+31
View File
@@ -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"] }),
);
+23
View File
@@ -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"] }),
);
+34
View File
@@ -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"] }),
);
+6
View File
@@ -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";
+68
View File
@@ -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"] }),
);
+3 -1
View File
@@ -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",
},
},
};
+17 -6
View File
@@ -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,
+92 -12
View File
@@ -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,
};
}
}
+4
View File
@@ -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);
},
+2
View File
@@ -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
View File
@@ -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",
},
+25 -9
View File
@@ -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");
+1
View File
@@ -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;
+41 -1
View File
@@ -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"],
},
],
},