{"record":{"id":"ca22f9bd996e1974","repo":"RocketChat/Rocket.Chat","slug":"error-keys-already-set","errorCode":"error-keys-already-set","errorMessage":"Keys already set","messagePattern":"Keys already set","errorType":"exception","errorClass":"Meteor.Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/server/meteor-methods/platform/setUserPublicAndPrivateKeys.ts","lineNumber":27,"sourceCode":"\t// eslint-disable-next-line @typescript-eslint/naming-convention\n\tinterface ServerMethods {\n\t\t'e2e.setUserPublicAndPrivateKeys'({ public_key, private_key }: { public_key: string; private_key: string; force?: boolean }): void;\n\t}\n}\n\nconst isKeysResult = (result: any): result is { public_key: string; private_key: string } => {\n\treturn result.private_key && result.public_key;\n};\n\nexport const setUserPublicAndPrivateKeysMethod = async (\n\tuserId: string,\n\tkeyPair: { public_key: string; private_key: string; force?: boolean },\n): Promise<void> => {\n\tif (!keyPair.force) {\n\t\tconst keys = await Users.fetchKeysByUserId(userId);\n\n\t\tif (isKeysResult(keys)) {\n\t\t\tthrow new Meteor.Error('error-keys-already-set', 'Keys already set', {\n\t\t\t\tmethod: 'e2e.setUserPublicAndPrivateKeys',\n\t\t\t});\n\t\t}\n\t}\n\n\tawait Users.setE2EPublicAndPrivateKeysByUserId(userId, {\n\t\tprivate_key: keyPair.private_key,\n\t\tpublic_key: keyPair.public_key,\n\t});\n\n\tconst subscribedRoomIds = await Rooms.getSubscribedRoomIdsWithoutE2EKeys(userId);\n\tawait Rooms.addUserIdToE2EEQueueByRoomIds(subscribedRoomIds, userId);\n\n\tvoid notifyOnRoomChangedById(subscribedRoomIds);\n};\n\nMeteor.methods<ServerMethods>({\n\tasync 'e2e.setUserPublicAndPrivateKeys'(keyPair) {","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/meteor-methods/platform/setUserPublicAndPrivateKeys.ts#L9-L45","documentation":"`e2e.setUserPublicAndPrivateKeys` throws `error-keys-already-set` when the user already has both a public and a private key stored (`Users.fetchKeysByUserId` returns both) and the request does not carry `force: true`. The guard prevents silently overwriting an existing E2E identity; regeneration must be explicit. Deprecated since 9.0.0 in favor of `/v1/e2e.setUserPublicAndPrivateKeys`.","triggerScenarios":"Publishing a key pair without `force` for a user whose keys already exist — a retried setup step, a second device generating fresh keys instead of fetching the existing pair, or re-running the post-login key setup.","commonSituations":"Retry after timeout where the first publish actually committed; new-device login re-running key generation; post-password-reset flows forgetting the force flag.","solutions":["Fetch the existing keys first (`e2e.getUserPublicAndPrivateKeys`) and reuse them instead of publishing new ones","When regeneration is intended (e.g. after a password reset), pass `force: true`","Make the client publish step idempotent — record completion and never re-run it"],"exampleFix":"// before\nMeteor.call('e2e.setUserPublicAndPrivateKeys', { public_key, private_key });\n// after\nMeteor.call('e2e.setUserPublicAndPrivateKeys', {\n  public_key,\n  private_key,\n  force: isIntentionalRegeneration,\n});","handlingStrategy":"validation","validationCode":"Meteor.call('e2e.getUserPublicAndPrivateKeys', (err, keys) => {\n  if (keys?.public_key && keys?.private_key) {\n    return useExistingKeys(keys); // reuse — do not publish\n  }\n  Meteor.call('e2e.setUserPublicAndPrivateKeys', { public_key, private_key });\n});","typeGuard":"const hasKeyPair = (keys: unknown): keys is { public_key: string; private_key: string } =>\n  !!keys && typeof keys === 'object' &&\n  !!(keys as any).public_key && !!(keys as any).private_key;","tryCatchPattern":"try {\n  await Meteor.callAsync('e2e.setUserPublicAndPrivateKeys', { public_key, private_key, force });\n} catch (e) {\n  if (e instanceof Meteor.Error && e.error === 'error-keys-already-set') {\n    // fetch existing keys and reuse, or resend with force: true if regenerating\n  }\n}","preventionTips":["Always fetch existing keys before generating and publishing new ones","Mark the publish step done so retries never re-send it","Reserve force: true for deliberate regeneration (password reset flows)"],"tags":["e2ee","key-management","idempotency","deprecated","meteor-method"],"backgroundTag":"e2e-keys-already-set","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}