Changed "token" param to "hash" and used hex encoding for URL

This commit is contained in:
x032205
2025-05-19 10:48:58 -04:00
parent 9fd37ca456
commit 0401793d38
6 changed files with 21 additions and 21 deletions
@@ -64,7 +64,7 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
hashedHex: z.string().min(1).optional(), hashedHex: z.string().min(1).optional(),
password: z.string().optional(), password: z.string().optional(),
email: z.string().optional(), email: z.string().optional(),
token: z.string().optional() hash: z.string().optional()
}), }),
response: { response: {
200: z.object({ 200: z.object({
@@ -92,7 +92,7 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
password: req.body.password, password: req.body.password,
orgId: req.permission?.orgId, orgId: req.permission?.orgId,
email: req.body.email, email: req.body.email,
token: req.body.token hash: req.body.hash
}); });
if (sharedSecret.secret?.orgId) { if (sharedSecret.secret?.orgId) {
@@ -118,7 +118,7 @@ export const secretSharingServiceFactory = ({
} }
} }
// Generate salt for signing email tokens (if emails are provided) // Generate salt for signing email hashes (if emails are provided)
salt = crypto.randomBytes(32).toString("hex"); salt = crypto.randomBytes(32).toString("hex");
encryptedSalt = encryptWithRoot(Buffer.from(salt)); encryptedSalt = encryptWithRoot(Buffer.from(salt));
} }
@@ -159,7 +159,7 @@ export const secretSharingServiceFactory = ({
for await (const email of emails) { for await (const email of emails) {
try { try {
const hmac = crypto.createHmac("sha256", salt).update(email); const hmac = crypto.createHmac("sha256", salt).update(email);
const token = hmac.digest("base64"); const hash = hmac.digest("hex");
// Only show the username to emails which are part of the organization // Only show the username to emails which are part of the organization
const respondentUsername = orgEmails.includes(email) ? user.username : undefined; const respondentUsername = orgEmails.includes(email) ? user.username : undefined;
@@ -170,7 +170,7 @@ export const secretSharingServiceFactory = ({
substitutions: { substitutions: {
name, name,
respondentUsername, respondentUsername,
secretRequestUrl: `${appCfg.SITE_URL}/shared/secret/${idToReturn}?email=${encodeURIComponent(email)}&token=${token}` secretRequestUrl: `${appCfg.SITE_URL}/shared/secret/${idToReturn}?email=${encodeURIComponent(email)}&hash=${hash}`
}, },
template: SmtpTemplates.SecretRequestCompleted template: SmtpTemplates.SecretRequestCompleted
}); });
@@ -460,7 +460,7 @@ export const secretSharingServiceFactory = ({
orgId, orgId,
password, password,
email, email,
token hash
}: TGetActiveSharedSecretByIdDTO) => { }: TGetActiveSharedSecretByIdDTO) => {
const sharedSecret = isUuidV4(sharedSecretId) const sharedSecret = isUuidV4(sharedSecretId)
? await secretSharingDAL.findOne({ ? await secretSharingDAL.findOne({
@@ -512,22 +512,22 @@ export const secretSharingServiceFactory = ({
if (sharedSecret.authorizedEmails && sharedSecret.encryptedSalt) { if (sharedSecret.authorizedEmails && sharedSecret.encryptedSalt) {
// Verify both params were passed // Verify both params were passed
if (!email || !token) { if (!email || !hash) {
throw new BadRequestError({ throw new BadRequestError({
message: "This secret is email protected. Parameters must include email and token." message: "This secret is email protected. Parameters must include email and hash."
}); });
// Verify that email is authorized to view shared secret // Verify that email is authorized to view shared secret
} else if (!(sharedSecret.authorizedEmails as string[]).includes(email)) { } else if (!(sharedSecret.authorizedEmails as string[]).includes(email)) {
throw new UnauthorizedError({ message: "Email not authorized to view secret" }); throw new UnauthorizedError({ message: "Email not authorized to view secret" });
// Verify that token matches // Verify that hash matches
} else { } else {
const salt = decryptWithRoot(sharedSecret.encryptedSalt).toString(); const salt = decryptWithRoot(sharedSecret.encryptedSalt).toString();
const hmac = crypto.createHmac("sha256", salt).update(email); const hmac = crypto.createHmac("sha256", salt).update(email);
const rebuiltToken = hmac.digest("base64"); const rebuiltHash = hmac.digest("hex");
if (rebuiltToken !== token) { if (rebuiltHash !== hash) {
throw new UnauthorizedError({ message: "Email not authorized to view secret" }); throw new UnauthorizedError({ message: "Email not authorized to view secret" });
} }
} }
@@ -41,7 +41,7 @@ export type TGetActiveSharedSecretByIdDTO = {
// For secrets shared with specific emails // For secrets shared with specific emails
email?: string; email?: string;
token?: string; hash?: string;
}; };
export type TValidateActiveSharedSecretDTO = TGetActiveSharedSecretByIdDTO & { export type TValidateActiveSharedSecretDTO = TGetActiveSharedSecretByIdDTO & {
@@ -16,7 +16,7 @@ export const secretSharingKeys = {
hashedHex: string | null; hashedHex: string | null;
password?: string; password?: string;
email?: string; email?: string;
token?: string; hash?: string;
}) => ["shared-secret", arg], }) => ["shared-secret", arg],
getSecretRequestById: (arg: { id: string }) => ["secret-request", arg] as const getSecretRequestById: (arg: { id: string }) => ["secret-request", arg] as const
}; };
@@ -75,7 +75,7 @@ export const useGetActiveSharedSecretById = ({
hashedHex, hashedHex,
password, password,
email, email,
token hash
}: { }: {
sharedSecretId: string; sharedSecretId: string;
hashedHex: string | null; hashedHex: string | null;
@@ -83,7 +83,7 @@ export const useGetActiveSharedSecretById = ({
// For secrets shared to specific emails (optional) // For secrets shared to specific emails (optional)
email?: string; email?: string;
token?: string; hash?: string;
}) => { }) => {
return useQuery({ return useQuery({
queryKey: secretSharingKeys.getSecretById({ queryKey: secretSharingKeys.getSecretById({
@@ -91,7 +91,7 @@ export const useGetActiveSharedSecretById = ({
hashedHex, hashedHex,
password, password,
email, email,
token hash
}), }),
queryFn: async () => { queryFn: async () => {
const { data } = await apiRequest.post<TViewSharedSecretResponse>( const { data } = await apiRequest.post<TViewSharedSecretResponse>(
@@ -100,7 +100,7 @@ export const useGetActiveSharedSecretById = ({
...(hashedHex && { hashedHex }), ...(hashedHex && { hashedHex }),
password, password,
email, email,
token hash
} }
); );
@@ -42,9 +42,9 @@ export const ViewSharedSecretByIDPage = () => {
from: ROUTE_PATHS.Public.ViewSharedSecretByIDPage.id, from: ROUTE_PATHS.Public.ViewSharedSecretByIDPage.id,
select: (el) => el.email select: (el) => el.email
}); });
const token = useSearch({ const hash = useSearch({
from: ROUTE_PATHS.Public.ViewSharedSecretByIDPage.id, from: ROUTE_PATHS.Public.ViewSharedSecretByIDPage.id,
select: (el) => el.token select: (el) => el.hash
}); });
const [password, setPassword] = useState<string>(); const [password, setPassword] = useState<string>();
const { hashedHex, key } = extractDetailsFromUrl(urlEncodedKey); const { hashedHex, key } = extractDetailsFromUrl(urlEncodedKey);
@@ -59,7 +59,7 @@ export const ViewSharedSecretByIDPage = () => {
hashedHex, hashedHex,
password, password,
email, email,
token hash
}); });
const navigate = useNavigate(); const navigate = useNavigate();
@@ -9,7 +9,7 @@ import { ViewSharedSecretByIDPage } from "./ViewSharedSecretByIDPage";
const SharedSecretByIDPageQuerySchema = z.object({ const SharedSecretByIDPageQuerySchema = z.object({
key: z.string().catch(""), key: z.string().catch(""),
email: z.string().optional(), email: z.string().optional(),
token: z.string().optional() hash: z.string().optional()
}); });
export const Route = createFileRoute("/shared/secret/$secretId")({ export const Route = createFileRoute("/shared/secret/$secretId")({