Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { AuthenticatedAction } from "@lib/actions";
import { ServerActionError } from "@lib/types/form-builder-types";
import { logMessage } from "@root/lib/logger";
import { prisma, prismaErrors } from "@lib/integration/prismaConnector";
import { AuditLogDetails, logEvent } from "@root/lib/auditLogs";
import { AuditLogDetails, logEvent, AuditLogEvent } from "@root/lib/auditLogs";
import { getNotificationsUsers } from "@root/lib/notifications";

// Public facing functions - they can be used by anyone who finds the associated server action identifer
Expand Down Expand Up @@ -35,7 +35,7 @@ export const updateNotificationsUser = AuthenticatedAction(
logEvent(
session.user.id,
{ type: "Form", id: formId },
"UpdateNotificationsUserSetting",
AuditLogEvent.UpdatedNotificationSettings,
AuditLogDetails.UpdatedNotificationSettings,
{ userId: session.user.id, formId, enabled: user.enabled ? "enabled" : "disabled" }
);
Expand Down
11 changes: 6 additions & 5 deletions i18n/translations/en/form-builder.json
Original file line number Diff line number Diff line change
Expand Up @@ -503,20 +503,21 @@
},
"events": {
"CreateForm": "Form created",
"DeleteForm": "Form archived",
"UnarchiveForm": "Form restored",
"ChangeDeliveryOption": "Delivery option changed",
"ChangeSecurityAttribute": "Data classification changed",
"ChangeFormPurpose": "Intended use changed",
"ChangeFormSaveAndResume": "Save and resume option changed",
"ChangeFormName": "Form name changed",
"UpdateForm": "Form updated",
"UpdateNotificationsInterval": "Notifications interval changed",
"UpdateNotificationsUserSetting": "Notifications user setting changed",
"GrantFormAccess": "Granted form access",
"RevokeFormAccess": "Revoked form access",
"DownloadResponse": "Downloaded responses",
"ConfirmResponse": "Confirmed responses",
"IdentifyProblemResponse": "Reported problem with responses",
"DeleteResponses": "Deleted responses",
"DeleteDraftResponses": "Test responses cleared while publishing",
"RetrieveResponses": "Retrieved responses via API",
"CreateAPIKey": "Generated API Key",
"DeleteAPIKey": "Deleted API Key",
Expand All @@ -536,7 +537,7 @@
"ChangeSecurityAttribute": "Changed classification to {{securityAttribute}}",
"SetFormPurpose": "Set intended use as {{formPurpose}}",
"SetSaveAndResume": "Saving progress set to {{saveAndResume}}",
"UpdateClosingDate": "Updated closing date",
"UpdateClosingDate": "Updated closing date to {{closingDate}}",
"UpdatedNotificationSettings": "Updated notifications setting to {{enabled}} for {{userEmail}}",
"GeneratedAPIKey": "API key {{APIkeyID}} generated by {{userEmail}}",
"DeletedAPIKey": "API key {{APIkeyID}} deleted by {{userEmail}}",
Expand All @@ -555,8 +556,8 @@
"Unclassified": "Unclassified",
"Protected A": "Protected A",
"Protected B": "Protected B",
"Administrative": "Administrative",
"Non-administrative": "Non-administrative",
"admin": "Administrative",
"nonAdmin": "Non-administrative",
"On": "On",
"Off": "Off",
"500 requests per minute": "500 requests per minute",
Expand Down
11 changes: 6 additions & 5 deletions i18n/translations/fr/form-builder.json
Original file line number Diff line number Diff line change
Expand Up @@ -503,20 +503,21 @@
},
"events": {
"CreateForm": "Formulaire créé",
"DeleteForm": "Formulaire archivé",
"UnarchiveForm": "Formulaire désarchivé",
"ChangeDeliveryOption": "Option de livraison modifiée",
"ChangeSecurityAttribute": "Classification des données modifiée",
"ChangeFormPurpose": "Usage prévu modifié",
"ChangeFormSaveAndResume": "Option de sauvegarde et de reprise modifiée",
"ChangeFormName": "Nom du formulaire modifié",
"UpdateForm": "Formulaire mis à jour",
"UpdateNotificationsInterval": "Intervalle de notifications modifié",
"UpdateNotificationsUserSetting": "Paramètre utilisateur des notification modifié",
"GrantFormAccess": "Accès au formulaire accordé",
"RevokeFormAccess": "Accès au formulaire révoqué",
"DownloadResponse": "Réponses téléchargées",
"ConfirmResponse": "Réponses confirmées",
"IdentifyProblemResponse": "Réponses signalées avec probléme",
"DeleteResponses": "Réponses supprimées",
"DeleteDraftResponses": "Réponses tests effacées lors de la publication.",
"RetrieveResponses": "Réponses récupérées via l'API",
"CreateAPIKey": "Clé API générée",
"DeleteAPIKey": "Clé API supprimée",
Expand All @@ -536,7 +537,7 @@
"ChangeSecurityAttribute": "Classification changée à {{securityAttribute}}",
"SetFormPurpose": "Usage prévu défini comme {{formPurpose}}",
"SetSaveAndResume": "La sauvegarde des réponses est {{saveAndResume}}",
"UpdateClosingDate": "Date de fermeture mise à jour",
"UpdateClosingDate": "Date de fermeture mise à jour à {{closingDate}}",
"UpdatedNotificationSettings": "Paramètre de notification mis à jour à {{enabled}} pour {{userEmail}}",
"GeneratedAPIKey": "Clé API {{APIkeyID}} générée par {{userEmail}}",
"DeletedAPIKey": "Clé API {{APIkeyID}} supprimée par {{userEmail}}",
Expand All @@ -555,8 +556,8 @@
"Unclassified": "Non classifié",
"Protected A": "Protégé A",
"Protected B": "Protégé B",
"Administrative": "Administratif",
"Non-administrative": "Non administratif",
"admin": "Administratif",
"nonAdmin": "Non administratif",
"On": "Activée",
"Off": "Désactivée",
"500 requests per minute": "500 demandes par minute",
Expand Down
14 changes: 7 additions & 7 deletions lib/auditLogs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const AuditLogEvent = {
GrantFormAccess: "GrantFormAccess",
RevokeFormAccess: "RevokeFormAccess",
UpdateNotificationsInterval: "UpdateNotificationsInterval",
UpdateNotificationsUserSetting: "UpdateNotificationsUserSetting",
UpdatedNotificationSettings: "UpdatedNotificationSettings",
UpdateFormJsonConfig: "updateFormJsonConfig",
// Invitations
InvitationCreated: "InvitationCreated",
Expand All @@ -41,7 +41,7 @@ export const AuditLogEvent = {
ConfirmResponse: "ConfirmResponse",
IdentifyProblemResponse: "IdentifyProblemResponse",
ListResponses: "ListResponses",
DeleteResponses: "DeleteResponses",
DeleteDraftResponses: "DeleteDraftResponses",
RetrieveResponses: "RetrieveResponses",
// User Events
UserRegistration: "UserRegistration",
Expand Down Expand Up @@ -75,7 +75,7 @@ export const AuditLogEvent = {
AuditLogsRead: "AuditLogsRead",
} as const;

export type AuditLogEventStrings = keyof typeof AuditLogEvent;
export type AuditLogEvent = keyof typeof AuditLogEvent;

export const AuditSubjectType = {
User: "User",
Expand Down Expand Up @@ -104,7 +104,7 @@ export const AuditLogDetails = {
CancelInvitation: "CancelInvitation",
UserInvited: "UserInvited",
CognitoUserIdentifier: "Cognito user unique identifier (sub): ${userId}",
UpdatedNotificationSettings: "`UpdatedNotificationSettings",
UpdatedNotificationSettings: "UpdatedNotificationSettings",
ConfirmedResponsesForForm: "ConfirmedResponsesForForm",
DeletedDraftResponsesForForm: "Deleted draft responses for form ${formId}",
RetreiveSelectedFormResponses:
Expand Down Expand Up @@ -153,8 +153,8 @@ type AuditDetailsParams = {
[AuditLogDetails.IncreasedThrottling]: { userId: string; formId: string; weeks: string };
[AuditLogDetails.PermanentIncreasedThrottling]: { userId: string; formId: string };
[AuditLogDetails.ResetThrottling]: { userId: string; formId: string };
[AuditLogDetails.DeclinedInvitation]: { userId: string };
[AuditLogDetails.AcceptedInvitation]: { userId: string };
[AuditLogDetails.DeclinedInvitation]: { userEmail: string };
[AuditLogDetails.AcceptedInvitation]: { userEmail: string };
[AuditLogDetails.AccessGranted]: { grantedUserId: string };
[AuditLogDetails.CancelInvitation]: { userId: string; invitationEmail: string };
[AuditLogDetails.UserInvited]: { userEmail: string; invitationEmail: string };
Expand Down Expand Up @@ -366,7 +366,7 @@ const resolveDescription = (
export const logEvent = async <T extends keyof AllAuditParams | undefined = undefined>(
userId: string,
subject: { type: keyof typeof AuditSubjectType; id?: string },
event: AuditLogEventStrings,
event: AuditLogEvent,
...args: T extends keyof AllAuditParams
? AllAuditParams[T] extends never
? [description: T]
Expand Down
6 changes: 3 additions & 3 deletions lib/invitations/acceptInvitation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
UserNotFoundError,
} from "./exceptions";
import { getAbility } from "@lib/privileges";
import { AuditLogDetails, logEvent } from "@lib/auditLogs";
import { AuditLogDetails, AuditLogEvent, logEvent } from "@lib/auditLogs";
import { notifyOwnersOwnerAdded } from "@lib/templates";
import { logMessage } from "@lib/logger";
import { AccessControlError } from "@lib/auth/errors";
Expand Down Expand Up @@ -86,9 +86,9 @@ export const acceptInvitation = async (invitationId: string) => {
logEvent(
ability.user.id,
{ type: "Form", id: invitation.templateId },
"InvitationAccepted",
AuditLogEvent.InvitationAccepted,
AuditLogDetails.AcceptedInvitation,
{ userId: user.id }
{ userEmail: user.email }
);

// some existing events may not yet have the 'invitedBy' attribute.
Expand Down
6 changes: 3 additions & 3 deletions lib/invitations/declineInvitation.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { prisma } from "@lib/integration/prismaConnector";
import { InvitationNotFoundError, UserNotFoundError } from "./exceptions";
import { getUser } from "@lib/users";
import { AuditLogDetails, logEvent } from "@lib/auditLogs";
import { AuditLogDetails, logEvent, AuditLogEvent } from "@lib/auditLogs";
import { getAbility } from "@lib/privileges";
import { logMessage } from "@lib/logger";
import { AccessControlError } from "@lib/auth/errors";
Expand Down Expand Up @@ -43,9 +43,9 @@ export const declineInvitation = async (invitationId: string) => {
logEvent(
ability.user.id,
{ type: "Form", id: invitation.templateId },
"InvitationDeclined",
AuditLogEvent.InvitationDeclined,
AuditLogDetails.DeclinedInvitation,
{ userId: user.id }
{ userEmail: user.email }
);
};

Expand Down
5 changes: 4 additions & 1 deletion lib/invitations/tests/invitations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ jest.mock("@lib/auditLogs", () => ({
get AuditLogDetails() {
return jest.requireActual("@lib/auditLogs").AuditLogDetails;
},
get AuditLogEvent() {
return jest.requireActual("@lib/auditLogs").AuditLogEvent;
},
get AuditLogAccessDeniedDetails() {
return jest.requireActual("@lib/auditLogs").AuditLogAccessDeniedDetails;
}
Expand Down Expand Up @@ -355,7 +358,7 @@ describe("Invitations", () => {
{ id: "template-id", type: "Form" },
"InvitationAccepted",
"AcceptedInvitation",
{ "userId" : "invited-user-id" }
{ "userEmail" : "invited@cds-snc.ca" }
);

// @TODO: these tests need to move to templates.test.ts
Expand Down
49 changes: 35 additions & 14 deletions lib/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from "@lib/types";
import { Prisma } from "@prisma/client";
import { authorization, getAbility } from "./privileges";
import { AuditLogAccessDeniedDetails, AuditLogDetails, logEvent } from "./auditLogs";
import { AuditLogAccessDeniedDetails, AuditLogDetails, AuditLogEvent, logEvent } from "./auditLogs";
import { logMessage } from "@lib/logger";
import { unprocessedSubmissions, deleteDraftFormResponses } from "./vault";
import { deleteKey } from "./serviceAccount";
Expand Down Expand Up @@ -547,6 +547,17 @@ export async function updateTemplate(command: UpdateTemplateCommand): Promise<Fo
throw new InvalidFormConfigError();
}

const currentTemplate = await prisma.template.findUnique({
where: {
id: command.formID,
},
select: {
name: true,
deliveryOption: true,
securityAttribute: true,
},
});

const updatedTemplate = await prisma.template
.update({
where: {
Expand Down Expand Up @@ -591,14 +602,17 @@ export async function updateTemplate(command: UpdateTemplateCommand): Promise<Fo
if (formCache.cacheAvailable) formCache.invalidate(command.formID);

// Log the audit events
logEvent(
user.id,
{ type: "Form", id: command.formID },
"ChangeFormName",
AuditLogDetails.UpdatedFormName,
{ newFormName: command.name ?? "" }
);
command.name !== undefined &&
(currentTemplate?.name ?? "") !== command.name &&
logEvent(
user.id,
{ type: "Form", id: command.formID },
AuditLogEvent.ChangeFormName,
AuditLogDetails.UpdatedFormName,
{ newFormName: command.name ?? "" }
);
command.deliveryOption &&
command.deliveryOption !== currentTemplate?.deliveryOption &&
logEvent(
user.id,
{ type: "Form", id: command.formID },
Expand All @@ -611,10 +625,11 @@ export async function updateTemplate(command: UpdateTemplateCommand): Promise<Fo
}
);
command.securityAttribute &&
command.securityAttribute !== currentTemplate?.securityAttribute &&
logEvent(
user.id,
{ type: "Form", id: command.formID },
"ChangeSecurityAttribute",
AuditLogEvent.ChangeSecurityAttribute,
AuditLogDetails.ChangeSecurityAttribute,
{ securityAttribute: command.securityAttribute ?? "" }
);
Expand Down Expand Up @@ -1198,9 +1213,9 @@ export async function updateFormSaveAndResume(
logEvent(
user.id,
{ type: "Form", id: formID },
"ChangeFormSaveAndResume",
AuditLogEvent.ChangeFormSaveAndResume,
AuditLogDetails.SetSaveAndResume,
{ saveAndResume: String(saveAndResume) }
{ saveAndResume: saveAndResume ? "On" : "Off" }
);

return _parseTemplate(updatedTemplate);
Expand Down Expand Up @@ -1436,7 +1451,7 @@ export async function deleteTemplate(formID: string): Promise<FormRecord | null>
// There was an error with Prisma, do not delete from Cache.
if (templateMarkedAsDeleted === null) return templateMarkedAsDeleted;

logEvent(user.id, { type: "Form", id: formID }, "DeleteForm");
logEvent(user.id, { type: "Form", id: formID }, AuditLogEvent.DeleteForm);

if (formCache.cacheAvailable) formCache.invalidate(formID);

Expand Down Expand Up @@ -1517,7 +1532,7 @@ export async function restoreTemplate(formID: string): Promise<FormRecord | null
// There was an error with Prisma, do not delete from Cache.
if (templateMarkedToUnarchive === null) return templateMarkedToUnarchive;

logEvent(user.id, { type: "Form", id: formID }, "UnarchiveForm");
logEvent(user.id, { type: "Form", id: formID }, AuditLogEvent.UnarchiveForm);

if (formCache.cacheAvailable) formCache.invalidate(formID);

Expand Down Expand Up @@ -1640,7 +1655,13 @@ export const updateSecurityAttribute = async (formID: string, securityAttribute:

if (formCache.cacheAvailable) formCache.invalidate(formID);

logEvent(user.id, { type: "Form", id: formID }, "ChangeSecurityAttribute");
logEvent(
user.id,
{ type: "Form", id: formID },
AuditLogEvent.ChangeSecurityAttribute,
AuditLogDetails.ChangeSecurityAttribute,
{ securityAttribute: securityAttribute ?? "" }
);

return _parseTemplate(updatedTemplate);
};
Expand Down
3 changes: 3 additions & 0 deletions lib/tests/templates.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ jest.mock("@lib/auditLogs", () => ({
get AuditLogDetails() {
return jest.requireActual("@lib/auditLogs").AuditLogDetails;
},
get AuditLogEvent() {
return jest.requireActual("@lib/auditLogs").AuditLogEvent;
},
get AuditLogAccessDeniedDetails() {
return jest.requireActual("@lib/auditLogs").AuditLogAccessDeniedDetails;
}
Expand Down
3 changes: 3 additions & 0 deletions lib/tests/vault.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ jest.mock("@lib/auditLogs", () => ({
get AuditLogDetails() {
return jest.requireActual("@lib/auditLogs").AuditLogDetails;
},
get AuditLogEvent() {
return jest.requireActual("@lib/auditLogs").AuditLogEvent;
},
get AuditLogAccessDeniedDetails() {
return jest.requireActual("@lib/auditLogs").AuditLogAccessDeniedDetails;
}
Expand Down
4 changes: 2 additions & 2 deletions lib/vault.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
VaultSubmission,
StartFromExclusiveResponse,
} from "@lib/types";
import { AuditLogAccessDeniedDetails, AuditLogDetails, logEvent } from "./auditLogs";
import { AuditLogAccessDeniedDetails, AuditLogDetails, AuditLogEvent, logEvent } from "./auditLogs";
import {
unprocessedSubmissionsCacheCheck,
unprocessedSubmissionsCachePut,
Expand Down Expand Up @@ -581,7 +581,7 @@ export async function deleteDraftFormResponses(formID: string) {
logEvent(
user.id,
{ type: "Form", id: formID },
"DeleteResponses",
AuditLogEvent.DeleteDraftResponses,
AuditLogDetails.DeletedDraftResponsesForForm,
{ formId: formID }
);
Expand Down
Loading