{"_id":"@47ng/opaque-server","_rev":"15-780c37c59e196ae1b0e04ddf84bc6bb9","name":"@47ng/opaque-server","dist-tags":{"beta":"0.0.1-beta.16","latest":"2.1.5"},"versions":{"0.0.1-beta.7":{"name":"@47ng/opaque-server","collaborators":["Marcin Lazar <marcin.lazar@pm.me>","Hawkheart","r0kk3rz","François Best <npm.opaque@francoisbest.com>"],"description":"An implementation of the OPAQUE key exchange protocol in WASM(WebAssembly)","version":"0.0.1-beta.7","repository":{"type":"git","url":"git+https://github.com/47ng/opaque-wasm.git","directory":"packages/server"},"main":"opaque-server.js","types":"opaque-server.d.ts","keywords":["cryptography","crypto","opaque","passwords","authentication","wasm","js","browser","webassembly","node","wasm-pack","bindgen","wasm-bindgen"],"type":"commonjs","license":"(Apache-2.0 OR MIT)","publishConfig":{"access":"public"},"devDependencies":{"sceau":"^1.3.0"},"scripts":{},"bugs":{"url":"https://github.com/47ng/opaque-wasm/issues"},"homepage":"https://github.com/47ng/opaque-wasm#readme","_id":"@47ng/opaque-server@0.0.1-beta.7","_integrity":"sha512-yTAwT1/p3GQPnTDM0u2l9dixt4Qxvs2VNEPkjjWfbI6pw6QMZtQ6pYbFKlszsvGKm5dzQHqGCiKFIK3enjiLlw==","_resolved":"/tmp/9b72015d5286d7d2213d52613263a9e0/47ng-opaque-server-0.0.1-beta.7.tgz","_from":"file:47ng-opaque-server-0.0.1-beta.7.tgz","_nodeVersion":"18.13.0","_npmVersion":"8.19.3","dist":{"integrity":"sha512-yTAwT1/p3GQPnTDM0u2l9dixt4Qxvs2VNEPkjjWfbI6pw6QMZtQ6pYbFKlszsvGKm5dzQHqGCiKFIK3enjiLlw==","shasum":"d6e28e636c79aeb0abcb17610c16cda546957c45","tarball":"https://registry.npmjs.org/@47ng/opaque-server/-/opaque-server-0.0.1-beta.7.tgz","fileCount":6,"unpackedSize":261249,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIBhSvyljQLfBHydlm0CWYRvDm4lB9uJnx9WSQfQM+GkaAiEAqAxYM0QObBZUlP6dvp/DQEgANzN8wsaYjo/ZF9k9rag="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJj2Z00ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqLhA//R1DsyNzA89k0DGDrhWVbqTEMW/NiOr1NRLUZ9nqBRbXM5mA/\r\nPc5MQuWlSLMjbzV0ZjpoH6ux4e3MA4ta4HNC1JYcj+QnIldGOcOnN4rx4/Sm\r\nWJajvQ7iTmG/Lr2tHhCd7GCi1IQFZjHrKQ6ilWkn8kc5zq3M0EQQoW1ntfLu\r\nwxGl27nCI8qaD33Xpd+ezfNAvH06/hCpIdD+3ftnopC7cEfSzqsg4PHqCVCS\r\nQp3oz6NijTMRBI8HWeudxgIjuFwnnwRwIr8o2lRcdTP9cP6VjxkVqX/6r1Ps\r\ndYX1zTrXM9zvCD/gXxmO9acZRiM/xBoEHHbs4npSubtMeewGIhAkyxALiIim\r\neogxNwDBPH8yRjNHcW2LLJY4xZMdSDSzDNxGMA2oafKMPYByIRfF5jTONMl+\r\nP/9FyMWKWgUV7YScINuXDlGlMpgYDiFbe6sqJfnY0GZuEVbRfDSBZwu+j7LL\r\n512ts8GtImqKrWW+rzix2qFaPLKo4A7dWe2FM/eOEemnpXCR/BuVF8MKsjgf\r\nVABJjjlKX9BNnGNSIBfScMBpE1jey09MFqd8RWEL3DjLJC8T2fudZGQ4uMEc\r\nRTrTVaLJtmGmUXOGCZJYToHEUgNL8BPcRwhAr3Sjs2swcLvmXe/utU7QW033\r\nD1eqijknCzzY77Sp1Jz/cRmgvPyaISlKpqQ=\r\n=EGwU\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"franky47","email":"accounts@francoisbest.com"},"directories":{},"maintainers":[{"name":"franky47","email":"accounts@francoisbest.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/opaque-server_0.0.1-beta.7_1675205940483_0.8573604845019025"},"_hasShrinkwrap":false},"0.0.1-beta.8":{"name":"@47ng/opaque-server","collaborators":["Marcin Lazar <marcin.lazar@pm.me>","Hawkheart","r0kk3rz","François Best <npm.opaque@francoisbest.com>"],"description":"An implementation of the OPAQUE key exchange protocol in WASM(WebAssembly)","version":"0.0.1-beta.8","repository":{"type":"git","url":"git+https://github.com/47ng/opaque-wasm.git","directory":"packages/server"},"main":"opaque-server.js","types":"opaque-server.d.ts","keywords":["cryptography","crypto","opaque","passwords","authentication","wasm","js","browser","webassembly","node","wasm-pack","bindgen","wasm-bindgen"],"type":"commonjs","license":"(Apache-2.0 OR MIT)","publishConfig":{"access":"public"},"devDependencies":{"sceau":"^1.3.0"},"scripts":{},"readme":"# `@47ng/opaque-server`\n\nThe OPAQUE key exchange protocol in WASM (WebAssembly), for Node.js.\nThis implementation is based on [facebook/opaque-ke](https://github.com/facebook/opaque-ke).\n\nBuilt as CJS for Node.js from [47ng/opaque-wasm](https://github.com/47ng/opaque-wasm/tree/fork/47ng-opaque/do-not-merge-to-upstream)\n_(a fork of [marucjmar/opaque-wasm](https://github.com/marucjmar/opaque-wasm)\nto allow server/server WASM code splitting, statelessness for the server, and uses Ristretto rather than the NIST P-256 curve)_.\n\nClient (browser) counterpart is available in [`@47ng/opaque-client`](https://npmjs.com/package/@47ng/opaque-client).\n\n## Installation\n\n```\nnpm install @47ng/opaque-server\nyarn add @47ng/opaque-server\npnpm add @47ng/opaque-server\n```\n\n## Usage\n\nReference: [OPAQUE Protocol overview](https://www.ietf.org/archive/id/draft-irtf-cfrg-opaque-09.html#name-protocol-overview).\n\n### Setup\n\nYou'll need to create a `ServerSetup` first, which is to be treated as a secret.\n\nChanging it will invalidate your existing saved credentials, as it allows\nclients to also authenticate your server (mutual authentication).\nYou can treat it kind of like a signature private key.\n\n```ts\nimport { ServerSetup } from '@47ng/opaque-server'\nimport { hex } from '@47ng/codec'\n\nconst serverSetup = ServerSetup.serialize(new ServerSetup())\n\n// serverSetup is a byte array, you can convert it to whatever\n// string format suits your application (eg: hexadecimal here):\nconsole.info(`Generated OPAQUE server setup: ${hex.encode(serverSetup)}`)\n```\n\nWhen starting your server, assuming you obtain this serialized `ServerSetup`\nfrom a secure location (a secret management service like Hashicorp Vault,\na mounted file or an environment variable), you can hydrate it like so:\n\n```ts\nimport { ServerSetup } from '@47ng/opaque-server'\nimport { hex } from '@47ng/codec'\n\nconst serverSetup = ServerSetup.deserialize(\n  hex.decode(OPAQUE_SERIALIZED_SERVER_SETUP)\n)\n```\n\nYou will then pass this server setup to the Registration and Login handlers.\n\n### Registration (signup)\n\nOPAQUE requires two handshakes to perform a signup (technically one and a half,\nthe final response has no cryptographic use to the client):\n\n![Sequence diagram of the OPAQUE registration protocol](./README-1.svg \"Registration\")\n\n<br/>\n\nPseudo-code:\n\n```ts\nimport { HandleRegistration } from '@47ng/opaque-server'\nimport crypto from 'node:crypto'\n\n// Request handler 1 (example path: `/registration/request`)\nasync function opaqueRegistrationRequest(request, response) {\n  const registration = new HandleRegistration(serverSetup)\n  const registrationResponse = registration.start(\n    request.body.username,\n    request.body.registrationRequest\n  )\n  const nonce = crypto.randomBytes(32).toString('hex')\n  await keyValueStore.set({\n    key: nonce,\n    value: request.body.username,\n    ttl: 120, // Something short, here 2 minutes\n  })\n  response.send(registrationResponse)\n  registration.free()\n}\n\n// Request handler 2 (example path: `/registration/record`)\nasync function opaqueRegistrationRecord(request, response) {\n  const registration = new HandleRegistration(serverSetup)\n  // Do not trust client-provided usernames in the request body here:\n  // https://github.com/facebook/opaque-ke/issues/276#issuecomment-1162609521\n  const username = await keyValueStore.get({\n    key: request.body.nonce,\n  })\n  const passwordFile = registration.finish(request.body.registrationRecord)\n  await db.insert({\n    username,\n    passwordFile,\n  })\n  await keyValueStore.del({ key: request.body.nonce })\n  response.status(204).send()\n  registration.free()\n}\n```\n\n> _Note: registration doesn't perform key exchange/agreement,\n> so a login step is necessary after signup to establish a shared key._\n\n### Login\n\nOPAQUE requires two handshakes to perform a login.\n\nAt the end of the second handshake, the server will be able to use the key\nagreed upon, and the client will already have the same key, so you can start\nusing that key from the second response.\n\n![Sequence diagram of the OPAQUE login protocol](./README-2.svg \"Login\")\n\n<br/>\n\nPseudo-code:\n\n```ts\nimport { HandleLogin } from '@47ng/opaque-server'\n\n// Request handler 1 (example path: `/login/request`)\nasync function opaqueLoginRequest(request, response) {\n  const login = new HandleLogin(serverSetup)\n  const { passwordFile } = await db.findOne({\n    where: { username: request.body.username },\n  })\n  const loginResponse = login.start(\n    passwordFile,\n    request.body.username,\n    request.body.loginRequest\n  )\n  const loginState = login.serialize()\n  const nonce = crypto.randomBytes(32).toString('hex')\n  await keyValueStore.set({\n    key: nonce,\n    value: {\n      username: request.body.username,\n      loginState,\n    },\n    ttl: 120, // Something short, here 2 minutes\n  })\n  response.send({\n    nonce,\n    loginResponse,\n  })\n  login.free()\n}\n\n// Request handler 2 (example path: `/login/final`)\nasync function opaqueLoginFinal(request, response) {\n  const { username, loginState } = await keyValueStore.get({\n    key: request.body.nonce,\n  })\n  const login = HandleLogin.deserialize(loginState)\n  const sessionKey = login.finish(request.body.loginFinal)\n  await keyValueStore.del({ key: request.body.nonce })\n  response.setAuthCookiesFor(username)\n  response.send(...)\n  login.free()\n}\n```\n","readmeFilename":"README.md","bugs":{"url":"https://github.com/47ng/opaque-wasm/issues"},"homepage":"https://github.com/47ng/opaque-wasm#readme","_id":"@47ng/opaque-server@0.0.1-beta.8","_integrity":"sha512-6UKrTcMU22ZYMC0DMU13UvBWgA+fqgD+7ggRGoKUsg6vUtLTwxiT7GWgVoTd+/h7Ot/fYNpeLLkxjMEik8WE/g==","_resolved":"/tmp/e99686363d1088b370bfc8397e761e02/47ng-opaque-server-0.0.1-beta.8.tgz","_from":"file:47ng-opaque-server-0.0.1-beta.8.tgz","_nodeVersion":"18.13.0","_npmVersion":"8.19.3","dist":{"integrity":"sha512-6UKrTcMU22ZYMC0DMU13UvBWgA+fqgD+7ggRGoKUsg6vUtLTwxiT7GWgVoTd+/h7Ot/fYNpeLLkxjMEik8WE/g==","shasum":"5a6c0b293f43bac79a2ce543cea49545f982657f","tarball":"https://registry.npmjs.org/@47ng/opaque-server/-/opaque-server-0.0.1-beta.8.tgz","fileCount":6,"unpackedSize":259655,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDadvKqyOoiCLNjBDU8aojGYoui5Lw61tghXJiAsujPzQIgLufrpiKvnVBC6gxdmnKYlnpDJFvSzAkFUwNQWrQOA1c="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJj2aIeACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmoDYQ/+K6kiyMSZ9nHwSwbZOslmJSnJahsycw7D39PlvdXNMMyIG9+h\r\nBjuKWOKkqv6fL8gLKXya5yJViglNrQuLAunoJRCosMvUgcYN5Tx0lN11nOdI\r\nmSD9pXwPJ9N4xUQnqC7zhsXgOrmYFzlL4nJuSnGmmloZgp0Af0yMwpycCPjt\r\nDactno8V6xdE2BHEF/cd3c11BuND65CtHfNH8GagrU8wKXbmNn7mvzHaBDmC\r\ngybXiboRR94PvVopVSrOXAREkzB5b8A8+qeYO8/hZsvO9+XtZ89IVEtA6N2l\r\nd1Fr7sJW9lOZzWfQZLEOHZgex0FPJ788CFLZIF9cgmpYcnhscipvjxNKUxcj\r\nIYkYkY8j+1GD6pg5eFO4aoLKOsisTijV6a40+0NU1nNmm7M14ke+P5XpW4Hz\r\npmrmr3rPEQBArghEMteqxYBPLkzZmXlp/0GWCGjiUPncUoUvzU2r132bwyit\r\nQGpDuv7haGownXG0qmfqW7+GugqtIZcWOuJQwaILVsDXns+iR0/bR81kgCJr\r\nJCXn/MHj7fPDNEbDPdM7kmOk4Aw4tGUSaREcMzHp4/lzHCK3EXygGSjUnuFE\r\nCF9Wjd+k51ANiqD5wH6SIDIejxMXemlyzzMlaOPjM4bR0fMo8eEySO7Nq1St\r\nCm4a9TbYEfg0UiP1le6CZgph2DnwW41DwMg=\r\n=m2OY\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"franky47","email":"accounts@francoisbest.com"},"directories":{},"maintainers":[{"name":"franky47","email":"accounts@francoisbest.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/opaque-server_0.0.1-beta.8_1675207198168_0.8195535546078796"},"_hasShrinkwrap":false},"0.0.1-beta.9":{"name":"@47ng/opaque-server","collaborators":["Marcin Lazar <marcin.lazar@pm.me>","Hawkheart","r0kk3rz","François Best <npm.opaque@francoisbest.com>"],"description":"An implementation of the OPAQUE key exchange protocol in WASM(WebAssembly)","version":"0.0.1-beta.9","repository":{"type":"git","url":"git+https://github.com/47ng/opaque-wasm.git","directory":"packages/server"},"main":"opaque-server.js","types":"opaque-server.d.ts","keywords":["cryptography","crypto","opaque","passwords","authentication","wasm","js","browser","webassembly","node","wasm-pack","bindgen","wasm-bindgen"],"type":"commonjs","license":"(Apache-2.0 OR MIT)","publishConfig":{"access":"public"},"devDependencies":{"sceau":"^1.3.0"},"scripts":{},"readme":"# `@47ng/opaque-server`\n\nThe OPAQUE key exchange protocol in WASM (WebAssembly), for Node.js.\nThis implementation is based on [facebook/opaque-ke](https://github.com/facebook/opaque-ke).\n\nBuilt as CJS for Node.js from [47ng/opaque-wasm](https://github.com/47ng/opaque-wasm/tree/fork/47ng-opaque/do-not-merge-to-upstream)\n_(a fork of [marucjmar/opaque-wasm](https://github.com/marucjmar/opaque-wasm)\nto allow server/server WASM code splitting, statelessness for the server, and uses Ristretto rather than the NIST P-256 curve)_.\n\nClient (browser) counterpart is available in [`@47ng/opaque-client`](https://npmjs.com/package/@47ng/opaque-client).\n\n## Installation\n\n```\nnpm install @47ng/opaque-server\nyarn add @47ng/opaque-server\npnpm add @47ng/opaque-server\n```\n\n## Usage\n\nReference: [OPAQUE Protocol overview](https://www.ietf.org/archive/id/draft-irtf-cfrg-opaque-09.html#name-protocol-overview).\n\n### Setup\n\nYou'll need to create a `ServerSetup` first, which is to be treated as a secret.\n\nChanging it will invalidate your existing saved credentials, as it allows\nclients to also authenticate your server (mutual authentication).\nYou can treat it kind of like a signature private key.\n\n```ts\nimport { ServerSetup } from '@47ng/opaque-server'\nimport { hex } from '@47ng/codec'\n\nconst serverSetup = ServerSetup.serialize(new ServerSetup())\n\n// serverSetup is a byte array, you can convert it to whatever\n// string format suits your application (eg: hexadecimal here):\nconsole.info(`Generated OPAQUE server setup: ${hex.encode(serverSetup)}`)\n```\n\nWhen starting your server, assuming you obtain this serialized `ServerSetup`\nfrom a secure location (a secret management service like Hashicorp Vault,\na mounted file or an environment variable), you can hydrate it like so:\n\n```ts\nimport { ServerSetup } from '@47ng/opaque-server'\nimport { hex } from '@47ng/codec'\n\nconst serverSetup = ServerSetup.deserialize(\n  hex.decode(OPAQUE_SERIALIZED_SERVER_SETUP)\n)\n```\n\nYou will then pass this server setup to the Registration and Login handlers.\n\n### Registration (signup)\n\nOPAQUE requires two handshakes to perform a signup (technically one and a half,\nthe final response has no cryptographic use to the client):\n\n![Sequence diagram of the OPAQUE registration protocol](./README-1.svg \"Registration\")\n\n<br/>\n\nPseudo-code:\n\n```ts\nimport { HandleRegistration } from '@47ng/opaque-server'\nimport crypto from 'node:crypto'\n\n// Request handler 1 (example path: `/registration/request`)\nasync function opaqueRegistrationRequest(request, response) {\n  const registration = new HandleRegistration(serverSetup)\n  const registrationResponse = registration.start(\n    request.body.username,\n    request.body.registrationRequest\n  )\n  const nonce = crypto.randomBytes(32).toString('hex')\n  await keyValueStore.set({\n    key: nonce,\n    value: request.body.username,\n    ttl: 120, // Something short, here 2 minutes\n  })\n  response.send(registrationResponse)\n  registration.free()\n}\n\n// Request handler 2 (example path: `/registration/record`)\nasync function opaqueRegistrationRecord(request, response) {\n  const registration = new HandleRegistration(serverSetup)\n  // Do not trust client-provided usernames in the request body here:\n  // https://github.com/facebook/opaque-ke/issues/276#issuecomment-1162609521\n  const username = await keyValueStore.get({\n    key: request.body.nonce,\n  })\n  const passwordFile = registration.finish(request.body.registrationRecord)\n  await db.insert({\n    username,\n    passwordFile,\n  })\n  await keyValueStore.del({ key: request.body.nonce })\n  response.status(204).send()\n  registration.free()\n}\n```\n\n> _Note: registration doesn't perform key exchange/agreement,\n> so a login step is necessary after signup to establish a shared key._\n\n### Login\n\nOPAQUE requires two handshakes to perform a login.\n\nAt the end of the second handshake, the server will be able to use the key\nagreed upon, and the client will already have the same key, so you can start\nusing that key from the second response.\n\n![Sequence diagram of the OPAQUE login protocol](./README-2.svg \"Login\")\n\n<br/>\n\nPseudo-code:\n\n```ts\nimport { HandleLogin } from '@47ng/opaque-server'\n\n// Request handler 1 (example path: `/login/request`)\nasync function opaqueLoginRequest(request, response) {\n  const login = new HandleLogin(serverSetup)\n  const { passwordFile } = await db.findOne({\n    where: { username: request.body.username },\n  })\n  const loginResponse = login.start(\n    passwordFile,\n    request.body.username,\n    request.body.loginRequest\n  )\n  const loginState = login.serialize()\n  const nonce = crypto.randomBytes(32).toString('hex')\n  await keyValueStore.set({\n    key: nonce,\n    value: {\n      username: request.body.username,\n      loginState,\n    },\n    ttl: 120, // Something short, here 2 minutes\n  })\n  response.send({\n    nonce,\n    loginResponse,\n  })\n  login.free()\n}\n\n// Request handler 2 (example path: `/login/final`)\nasync function opaqueLoginFinal(request, response) {\n  const { username, loginState } = await keyValueStore.get({\n    key: request.body.nonce,\n  })\n  const login = HandleLogin.deserialize(loginState)\n  const sessionKey = login.finish(request.body.loginFinal)\n  await keyValueStore.del({ key: request.body.nonce })\n  response.setAuthCookiesFor(username)\n  response.send(...)\n  login.free()\n}\n```\n","readmeFilename":"README.md","bugs":{"url":"https://github.com/47ng/opaque-wasm/issues"},"homepage":"https://github.com/47ng/opaque-wasm#readme","_id":"@47ng/opaque-server@0.0.1-beta.9","_integrity":"sha512-PSPep9Z0jdfLLctTsQrD8u8ecHT7TKb9Ba4xUwvKc1saL6694vm9+1CaGQHX5RJmD0v9/Q4rUgxr5u7N9qgvqw==","_resolved":"/tmp/a805d0e1b60a51cd020bb04ce76cf1a3/47ng-opaque-server-0.0.1-beta.9.tgz","_from":"file:47ng-opaque-server-0.0.1-beta.9.tgz","_nodeVersion":"18.13.0","_npmVersion":"8.19.3","dist":{"integrity":"sha512-PSPep9Z0jdfLLctTsQrD8u8ecHT7TKb9Ba4xUwvKc1saL6694vm9+1CaGQHX5RJmD0v9/Q4rUgxr5u7N9qgvqw==","shasum":"1a63336268fff07f7e66a2775e91dc5210428f5f","tarball":"https://registry.npmjs.org/@47ng/opaque-server/-/opaque-server-0.0.1-beta.9.tgz","fileCount":8,"unpackedSize":315427,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDPeEEQNtANYMxZgyzRPYLUzhxwsGgrIIJwKuZUFycYxgIhAKYrhHta5A7aaGDLnl6T9RDDJkzN0Cun/Dpvpe3QP6yY"}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJj2aR1ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmogAxAAh8eS3pwM1VP0QLDiBmMJkdW2vqjG1KusLbAbhdsZs+zSZ9I1\r\nOK7+F9N4B2//g4uVGv33OEzo5V+nuIAbK8yunGrV5e6+tD6a6GmJ2EfGFkpm\r\nCoYh20smn0Nbe4izov8EHI+fJbP17FcVy08EysGm5KlcK3NsbP0NO7mgw0gQ\r\nVCnv2pN+OjVRxbxMX1ghIzUK4FHCxlD33y1XC38vMoTO7VTsA23TsMli97zh\r\nqlN1PXB+A9VZq1P4mRNt8l6riVH9VsiwlQDAJJRlJROe8T3j/y6hak9rXcZ/\r\nSDQCQuko/RN1/u49GKSSIsmnQkJvKx+nOarUtwTwGBqIxRG+0cdLD78r/rVw\r\nkjGhEMGSJ7UKuPlVskIfn3nfUbJFB3Iq+kKVLwyxegr4dlGqajUiSysGPcK6\r\nz80y0YvfPafcDz2XQiRkbk4B0T0MMPq+8UI4l7cgwXxErEjAnYeoLoy7SHhY\r\neSi2K/MPWKspAPxr7dd12VOvjlYqGu9nXpvTVf1VfykqOek7ouRonHOn2TH9\r\nmI/wp1WlX8TANzFti3NIKm2vLhOSpdgCfcCFqYWHnZpS82IwvRszQ3Lh1vgV\r\n+RKpRjJJuo6+t9PgaiVdDhEyXs3snl2INBepc/q2jTOJsZXipp8ylxDKBaxd\r\nW7pCVteoMn6SIiWBZRDl93BpncRSY8yTL4k=\r\n=UuSS\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"franky47","email":"accounts@francoisbest.com"},"directories":{},"maintainers":[{"name":"franky47","email":"accounts@francoisbest.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/opaque-server_0.0.1-beta.9_1675207797177_0.8626261048117998"},"_hasShrinkwrap":false},"0.0.1-beta.10":{"name":"@47ng/opaque-server","collaborators":["Marcin Lazar <marcin.lazar@pm.me>","Hawkheart","r0kk3rz","François Best <npm.opaque@francoisbest.com>"],"description":"An implementation of the OPAQUE key exchange protocol in WASM(WebAssembly)","version":"0.0.1-beta.10","repository":{"type":"git","url":"git+https://github.com/47ng/opaque.git","directory":"packages/server"},"main":"opaque-server.js","types":"opaque-server.d.ts","keywords":["cryptography","crypto","opaque","passwords","authentication","wasm","js","browser","webassembly","node","wasm-pack","bindgen","wasm-bindgen"],"type":"commonjs","license":"(Apache-2.0 OR MIT)","publishConfig":{"access":"public"},"devDependencies":{"sceau":"^1.3.0"},"scripts":{},"readme":"# `@47ng/opaque-server`\n\nThe OPAQUE key exchange protocol in WASM (WebAssembly), for Node.js.\nThis implementation is based on [facebook/opaque-ke](https://github.com/facebook/opaque-ke).\n\nBuilt as CJS for Node.js from [47ng/opaque-wasm](https://github.com/47ng/opaque-wasm/tree/fork/47ng-opaque/do-not-merge-to-upstream)\n_(a fork of [marucjmar/opaque-wasm](https://github.com/marucjmar/opaque-wasm)\nto allow server/server WASM code splitting, statelessness for the server, and uses Ristretto rather than the NIST P-256 curve)_.\n\nClient (browser) counterpart is available in [`@47ng/opaque-client`](https://npmjs.com/package/@47ng/opaque-client).\n\n## Installation\n\n```\nnpm install @47ng/opaque-server\nyarn add @47ng/opaque-server\npnpm add @47ng/opaque-server\n```\n\n## Usage\n\nReference: [OPAQUE Protocol overview](https://www.ietf.org/archive/id/draft-irtf-cfrg-opaque-09.html#name-protocol-overview).\n\n### Setup\n\nYou'll need to create a `ServerSetup` first, which is to be treated as a secret.\n\nChanging it will invalidate your existing saved credentials, as it allows\nclients to also authenticate your server (mutual authentication).\nYou can treat it kind of like a signature private key.\n\n```ts\nimport { ServerSetup } from '@47ng/opaque-server'\nimport { hex } from '@47ng/codec'\n\nconst serverSetup = ServerSetup.serialize(new ServerSetup())\n\n// serverSetup is a byte array, you can convert it to whatever\n// string format suits your application (eg: hexadecimal here):\nconsole.info(`Generated OPAQUE server setup: ${hex.encode(serverSetup)}`)\n```\n\nWhen starting your server, assuming you obtain this serialized `ServerSetup`\nfrom a secure location (a secret management service like Hashicorp Vault,\na mounted file or an environment variable), you can hydrate it like so:\n\n```ts\nimport { ServerSetup } from '@47ng/opaque-server'\nimport { hex } from '@47ng/codec'\n\nconst serverSetup = ServerSetup.deserialize(\n  hex.decode(OPAQUE_SERIALIZED_SERVER_SETUP)\n)\n```\n\nYou will then pass this server setup to the Registration and Login handlers.\n\n### Registration (signup)\n\nOPAQUE requires two handshakes to perform a signup (technically one and a half,\nthe final response has no cryptographic use to the client):\n\n```mermaid\nsequenceDiagram\n    %%{\n      init: {\n        \"messageFontFamily\": \"monospace\",\n        \"noteAlign\": \"left\"\n      }\n    }%%\n    accTitle: Registration\n    accDescr: Sequence diagram of the OPAQUE registration protocol\n    actor C as Client\n    participant S as Server\n    participant D as Database\n    autonumber\n    Note right of C: Client registration start\n    activate C\n    C->>+S: username, registrationRequest\n    deactivate C\n    Note right of S: Server registration start\n    S->>D: Generate nonce<br/>Save { nonce: username }<br/>with short TTL\n    S->>-C: nonce, registrationResponse\n    activate C\n    Note right of C: Client registration finish\n    C->>+S: nonce, registrationRecord\n    deactivate C\n    Note right of S: Server registration finish\n    S->>D: Save credentials<br/>Remove nonce\n    S->>-C: HTTP 204 (No Content)\n```\n\n<br/>\n\nPseudo-code:\n\n```ts\nimport { HandleRegistration } from '@47ng/opaque-server'\nimport crypto from 'node:crypto'\n\n// Request handler 1 (example path: `/registration/request`)\nasync function opaqueRegistrationRequest(request, response) {\n  const registration = new HandleRegistration(serverSetup)\n  const registrationResponse = registration.start(\n    request.body.username,\n    request.body.registrationRequest\n  )\n  const nonce = crypto.randomBytes(32).toString('hex')\n  await keyValueStore.set({\n    key: nonce,\n    value: request.body.username,\n    ttl: 120, // Something short, here 2 minutes\n  })\n  response.send(registrationResponse)\n  registration.free()\n}\n\n// Request handler 2 (example path: `/registration/record`)\nasync function opaqueRegistrationRecord(request, response) {\n  const registration = new HandleRegistration(serverSetup)\n  // Do not trust client-provided usernames in the request body here:\n  // https://github.com/facebook/opaque-ke/issues/276#issuecomment-1162609521\n  const username = await keyValueStore.get({\n    key: request.body.nonce,\n  })\n  const passwordFile = registration.finish(request.body.registrationRecord)\n  await db.insert({\n    username,\n    passwordFile,\n  })\n  await keyValueStore.del({ key: request.body.nonce })\n  response.status(204).send()\n  registration.free()\n}\n```\n\n> _Note: registration doesn't perform key exchange/agreement,\n> so a login step is necessary after signup to establish a shared key._\n\n### Login\n\nOPAQUE requires two handshakes to perform a login.\n\nAt the end of the second handshake, the server will be able to use the key\nagreed upon, and the client will already have the same key, so you can start\nusing that key from the second response.\n\n```mermaid\nsequenceDiagram\n    %%{\n      init: {\n        \"messageFontFamily\": \"monospace\",\n        \"noteAlign\": \"left\"\n      }\n    }%%\n    accTitle: Login\n    accDescr: Sequence diagram of the OPAQUE login protocol\n    actor C as Client\n    participant S as Server\n    participant D as Database\n    autonumber\n    Note right of C: Client login start\n    activate C\n    C->>+S: username, loginRequest\n    deactivate C\n    S->>+D: query user by username\n    D->>-S: Obtain credentials\n    Note right of S: Server login start\n    S->>D: Generate nonce<br/>Save { nonce: { loginState, username } }<br/>with a short TTL\n    S->>-C: nonce, loginReponse\n    activate C\n    Note right of C: Client login finish\n    Note right of C: Now the client can<br/>access the shared key\n    C->>+S: nonce, loginFinal\n    deactivate C\n    D->>S: Obtain { username, loginState }<br/>using the given nonce\n    Note right of S: Server login finish\n    Note right of S: Now the server can<br/>access the shared key\n    S-->>D: Clear nonce & loginState\n    S->>-C: Set cookies or return token\n```\n\n<br/>\n\nPseudo-code:\n\n```ts\nimport { HandleLogin } from '@47ng/opaque-server'\n\n// Request handler 1 (example path: `/login/request`)\nasync function opaqueLoginRequest(request, response) {\n  const login = new HandleLogin(serverSetup)\n  const { passwordFile } = await db.findOne({\n    where: { username: request.body.username },\n  })\n  const loginResponse = login.start(\n    passwordFile,\n    request.body.username,\n    request.body.loginRequest\n  )\n  const loginState = login.serialize()\n  const nonce = crypto.randomBytes(32).toString('hex')\n  await keyValueStore.set({\n    key: nonce,\n    value: {\n      username: request.body.username,\n      loginState,\n    },\n    ttl: 120, // Something short, here 2 minutes\n  })\n  response.send({\n    nonce,\n    loginResponse,\n  })\n  login.free()\n}\n\n// Request handler 2 (example path: `/login/final`)\nasync function opaqueLoginFinal(request, response) {\n  const { username, loginState } = await keyValueStore.get({\n    key: request.body.nonce,\n  })\n  const login = HandleLogin.deserialize(loginState)\n  const sessionKey = login.finish(request.body.loginFinal)\n  await keyValueStore.del({ key: request.body.nonce })\n  response.setAuthCookiesFor(username)\n  response.send(...)\n  login.free()\n}\n```\n","readmeFilename":"README.md","bugs":{"url":"https://github.com/47ng/opaque/issues"},"homepage":"https://github.com/47ng/opaque#readme","_id":"@47ng/opaque-server@0.0.1-beta.10","_integrity":"sha512-bu5zhWKK9ztdx4I6gE4sjMSPO6xwYvGArVufEfL0I1LzwtHxSeK7ep+uSsFWpYPERhITYtwRNcH19ct8h1H48A==","_resolved":"/tmp/3631e2d7a2bb55998f4c4aab759b762f/47ng-opaque-server-0.0.1-beta.10.tgz","_from":"file:47ng-opaque-server-0.0.1-beta.10.tgz","_nodeVersion":"18.13.0","_npmVersion":"8.19.3","dist":{"integrity":"sha512-bu5zhWKK9ztdx4I6gE4sjMSPO6xwYvGArVufEfL0I1LzwtHxSeK7ep+uSsFWpYPERhITYtwRNcH19ct8h1H48A==","shasum":"de51949e53df67de1247631dcfcb1f5420db0665","tarball":"https://registry.npmjs.org/@47ng/opaque-server/-/opaque-server-0.0.1-beta.10.tgz","fileCount":6,"unpackedSize":261439,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQClnI0yzktbwZE/fbP+mJ3IDWCjZpD4Mhe3hUDrc3S6GwIgEiJPtOh0+9s6RU6C0RFqs+4mZdgodTnwjERXzM+2Oa4="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJj2aalACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmpF3RAAhC3936JOoeOkLAAAsByPfonrjIjBEaxhvc8XbS2woFznvkOi\r\nZty9cgY3ueOMoQ2kj+EbTl3qz1xMQfZFAE9YtulZ0QWYB0XA3Gh89Bex3Qv4\r\nsJSvK1/rKcTD9fmeVFsjEu8HXKuPR0xRxdlcnJl9e0iYIFpoaFrzCZhJSrKk\r\njtUtSRe/zTMIs7qmkYmoiBnNPwJYFePqOERICdNR7//l7r28izqRW8wZutjX\r\ncrJ+AIaP1m6i8pykG+9GIoy9X+Pa9k9uw1dcq9kcnAcpmdyprCB7HLaWISkN\r\nt77VSXGuvWDYQKbi6by03O5XPtBwjodGGL0X3cg319o+FgDuGNqctT9HnT8Q\r\nqffDsCzAHBdIfrQB2fH8Ofs90fJdL04ItE36aQHX0d1FJ+LXR6vKMrunJo39\r\ne8DNzlRxGptKCndR9YU9BD0ErabjyYHETbWUSMyK5VvISNEkri7DOZhhuMsD\r\nneNY5ZHvzA8p7t44Av3XPVkfTGz+jnJ0W1FDF1fC2e+0XZrt0mWlE7tyBBv2\r\nLprtUVM8gMkDWPDILogEBH03FVKPQhqYbp9koDmAWdT6f5PFTYaYkcbnvZsT\r\na9m9VDc4uqN46vXmO67u0zzL8GLozThXdgbeltVI7wimv0R0WuNxT2MM26F2\r\nTiKu3FlSWm6UkaPcp0nXRqni/7xwTSr3MlE=\r\n=Kn6h\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"franky47","email":"accounts@francoisbest.com"},"directories":{},"maintainers":[{"name":"franky47","email":"accounts@francoisbest.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/opaque-server_0.0.1-beta.10_1675208357199_0.9519847018025211"},"_hasShrinkwrap":false},"0.0.1-beta.11":{"name":"@47ng/opaque-server","collaborators":["Marcin Lazar <marcin.lazar@pm.me>","Hawkheart","r0kk3rz","François Best <npm.opaque@francoisbest.com>"],"description":"An implementation of the OPAQUE key exchange protocol in WASM(WebAssembly)","version":"0.0.1-beta.11","repository":{"type":"git","url":"git+https://github.com/47ng/opaque.git","directory":"packages/server"},"main":"opaque-server.js","types":"opaque-server.d.ts","keywords":["cryptography","crypto","opaque","passwords","authentication","wasm","js","browser","webassembly","node","wasm-pack","bindgen","wasm-bindgen"],"type":"commonjs","license":"(Apache-2.0 OR MIT)","publishConfig":{"access":"public"},"devDependencies":{"sceau":"^1.3.0"},"scripts":{},"readme":"# `@47ng/opaque-server`\n\nThe OPAQUE key exchange protocol in WASM (WebAssembly), for Node.js.\nThis implementation is based on [facebook/opaque-ke](https://github.com/facebook/opaque-ke).\n\nBuilt as CJS for Node.js from [47ng/opaque-wasm](https://github.com/47ng/opaque-wasm/tree/fork/47ng-opaque/do-not-merge-to-upstream)\n_(a fork of [marucjmar/opaque-wasm](https://github.com/marucjmar/opaque-wasm)\nto allow server/server WASM code splitting, statelessness for the server, and uses Ristretto rather than the NIST P-256 curve)_.\n\nClient (browser) counterpart is available in [`@47ng/opaque-client`](https://npmjs.com/package/@47ng/opaque-client).\n\n## Installation\n\n```\nnpm install @47ng/opaque-server\nyarn add @47ng/opaque-server\npnpm add @47ng/opaque-server\n```\n\n## Usage\n\nThis implements the [OPAQUE protocol overview](https://github.com/47ng/opaque/blob/main/docs/opaque-protocol-overview.md)\nfor a stateless server and with recommended security practices.\n\n### Setup\n\nYou'll need to create a `ServerSetup` first, which is to be treated as a secret.\n\nChanging it will invalidate your existing saved credentials, as it allows\nclients to also authenticate your server (mutual authentication).\nYou can treat it kind of like a signature private key.\n\n```ts\nimport { ServerSetup } from '@47ng/opaque-server'\nimport { hex } from '@47ng/codec'\n\nconst serverSetup = ServerSetup.serialize(new ServerSetup())\n\n// serverSetup is a byte array, you can convert it to whatever\n// string format suits your application (eg: hexadecimal here):\nconsole.info(`Generated OPAQUE server setup: ${hex.encode(serverSetup)}`)\n```\n\nWhen starting your server, assuming you obtain this serialized `ServerSetup`\nfrom a secure location (a secret management service like Hashicorp Vault,\na mounted file or an environment variable), you can hydrate it like so:\n\n```ts\nimport { ServerSetup } from '@47ng/opaque-server'\nimport { hex } from '@47ng/codec'\n\nconst serverSetup = ServerSetup.deserialize(\n  hex.decode(OPAQUE_SERIALIZED_SERVER_SETUP)\n)\n```\n\nYou will then pass this server setup to the Registration and Login handlers.\n\n### Registration (signup)\n\nOPAQUE requires two handshakes to perform a signup (technically one and a half,\nthe final response has no cryptographic use to the client):\n\nPseudo-code:\n\n```ts\nimport { HandleRegistration } from '@47ng/opaque-server'\nimport crypto from 'node:crypto'\n\n// Request handler 1 (example path: `/registration/request`)\nasync function opaqueRegistrationRequest(request, response) {\n  const registration = new HandleRegistration(serverSetup)\n  const registrationResponse = registration.start(\n    request.body.username,\n    request.body.registrationRequest\n  )\n  const nonce = crypto.randomBytes(32).toString('hex')\n  await keyValueStore.set({\n    key: nonce,\n    value: request.body.username,\n    ttl: 120, // Something short, here 2 minutes\n  })\n  response.send(registrationResponse)\n  registration.free()\n}\n\n// Request handler 2 (example path: `/registration/record`)\nasync function opaqueRegistrationRecord(request, response) {\n  const registration = new HandleRegistration(serverSetup)\n  // Do not trust client-provided usernames in the request body here:\n  // https://github.com/facebook/opaque-ke/issues/276#issuecomment-1162609521\n  const username = await keyValueStore.get({\n    key: request.body.nonce,\n  })\n  const passwordFile = registration.finish(request.body.registrationRecord)\n  await db.insert({\n    username,\n    passwordFile,\n  })\n  await keyValueStore.del({ key: request.body.nonce })\n  response.status(204).send()\n  registration.free()\n}\n```\n\n> _Note: registration doesn't perform key exchange/agreement,\n> so a login step is necessary after signup to establish a shared key._\n\n### Login\n\nOPAQUE requires two handshakes to perform a login.\n\nAt the end of the second handshake, the server will be able to use the key\nagreed upon, and the client will already have the same key, so you can start\nusing that key from the second response.\n\nPseudo-code:\n\n```ts\nimport { HandleLogin } from '@47ng/opaque-server'\n\n// Request handler 1 (example path: `/login/request`)\nasync function opaqueLoginRequest(request, response) {\n  const login = new HandleLogin(serverSetup)\n  const { passwordFile } = await db.findOne({\n    where: { username: request.body.username },\n  })\n  const loginResponse = login.start(\n    passwordFile,\n    request.body.username,\n    request.body.loginRequest\n  )\n  const loginState = login.serialize()\n  const nonce = crypto.randomBytes(32).toString('hex')\n  await keyValueStore.set({\n    key: nonce,\n    value: {\n      username: request.body.username,\n      loginState,\n    },\n    ttl: 120, // Something short, here 2 minutes\n  })\n  response.send({\n    nonce,\n    loginResponse,\n  })\n  login.free()\n}\n\n// Request handler 2 (example path: `/login/final`)\nasync function opaqueLoginFinal(request, response) {\n  const { username, loginState } = await keyValueStore.get({\n    key: request.body.nonce,\n  })\n  const login = HandleLogin.deserialize(loginState)\n  const sessionKey = login.finish(request.body.loginFinal)\n  await keyValueStore.del({ key: request.body.nonce })\n  response.setAuthCookiesFor(username)\n  response.send(...)\n  login.free()\n}\n```\n","readmeFilename":"README.md","bugs":{"url":"https://github.com/47ng/opaque/issues"},"homepage":"https://github.com/47ng/opaque#readme","_id":"@47ng/opaque-server@0.0.1-beta.11","_integrity":"sha512-gglVhRiKYIgrhqVU7uW24d1q/dFQx1FcUDEC/y5ujvWuaS9f+AqTlBgTNEMx/s9LGaiQjyftPO3F3Y1MhcLCPg==","_resolved":"/tmp/d02d1b9186eae03d59ba5dc5fd134540/47ng-opaque-server-0.0.1-beta.11.tgz","_from":"file:47ng-opaque-server-0.0.1-beta.11.tgz","_nodeVersion":"18.13.0","_npmVersion":"8.19.3","dist":{"integrity":"sha512-gglVhRiKYIgrhqVU7uW24d1q/dFQx1FcUDEC/y5ujvWuaS9f+AqTlBgTNEMx/s9LGaiQjyftPO3F3Y1MhcLCPg==","shasum":"a8381d2633e62da9a76330ce13a22c485d1640fa","tarball":"https://registry.npmjs.org/@47ng/opaque-server/-/opaque-server-0.0.1-beta.11.tgz","fileCount":6,"unpackedSize":259555,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCk65ZHX3PzKEPzfpy1VMlBYjSf9W3BbLijwK8cY+DD5wIgREWloMvAnuYOuZvsU6eOFW0i67J0Tovbd5JN6a7BOVM="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJj2kiWACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmpbyhAAnKJkh5UiL72yee8e4LTUrY4cGTLngtT445amMBryWaGJ6wL3\r\nmm8HGYZGyv8RfJ0q5cuDgHvPoC/Bs7V0WqK4FK8UChDjxqVwf3P+7g6nvIqZ\r\ncIdsaGh0Vm/rTXDE2gEg1yKbg3Piot59uH2kMZenRaWtIgTiASPCHTCUa5PD\r\nhFL1svL0C8IeY5URBO+7Sgl1mAaEfFEn19ArflwNNBc7dkufGcylzBAfp80p\r\nJt0oaimuNZqgyqNftT6q99NFYgM8TQUL+wgYNrR1xig/Ca2vXc8+MpjJMR0l\r\n0P2ifdG7QoAWVzKJHjhoxhlgC9AQzakLQKS8q11Ou/IVzYVbVDODgocWWEZ5\r\n2G+KOrjk8E/9/BGsSjihF8VCrPKH2YU1Dnl804Lw0YgJyDLcr/74TcP2lO7b\r\nlAHuXchB9/QZBcI4VPTJKHNXPrdP2Mc+JagvXuckYDoa0h/8cA15zqvtROUF\r\n3pTTnH6QusNhQCGyx6wrBWKNUvNqB56+o3jzr+0gRBtvU0VpTsoj6oRnOjqP\r\njE1t+PGgyyNwu+QZURnamWLNELXGZUSxeiLcRy+kUOHl85w55PtD6akW5jEc\r\ng5ykm4ba7a3BHmwwioFmgG0qUfTQpRjzAl01a3eFHce3iB8KcW9Y+K1t9Ccg\r\nXByQlVwGb65Cb72PHGG/kVOXCdXbUIm605o=\r\n=tqw7\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"franky47","email":"accounts@francoisbest.com"},"directories":{},"maintainers":[{"name":"franky47","email":"accounts@francoisbest.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/opaque-server_0.0.1-beta.11_1675249814498_0.718521601214718"},"_hasShrinkwrap":false},"0.0.1-beta.13":{"name":"@47ng/opaque-server","collaborators":["Marcin Lazar <marcin.lazar@pm.me>","Hawkheart","r0kk3rz","François Best <npm.opaque@francoisbest.com>"],"description":"An implementation of the OPAQUE key exchange protocol in WASM(WebAssembly)","version":"0.0.1-beta.13","repository":{"type":"git","url":"git+https://github.com/47ng/opaque.git","directory":"packages/server"},"main":"opaque-server.js","types":"opaque-server.d.ts","keywords":["cryptography","crypto","opaque","passwords","authentication","wasm","js","browser","webassembly","node","wasm-pack","bindgen","wasm-bindgen"],"type":"commonjs","license":"(Apache-2.0 OR MIT)","publishConfig":{"access":"public"},"devDependencies":{"sceau":"^1.3.0"},"scripts":{},"readme":"# `@47ng/opaque-server`\n\nThe OPAQUE key exchange protocol in WASM (WebAssembly), for Node.js.\nThis implementation is based on [facebook/opaque-ke](https://github.com/facebook/opaque-ke).\n\nBuilt as CJS for Node.js from [47ng/opaque-wasm](https://github.com/47ng/opaque-wasm/tree/fork/47ng-opaque/do-not-merge-to-upstream)\n_(a fork of [marucjmar/opaque-wasm](https://github.com/marucjmar/opaque-wasm)\nto allow server/server WASM code splitting, statelessness for the server, and uses Ristretto rather than the NIST P-256 curve)_.\n\nClient (browser) counterpart is available in [`@47ng/opaque-client`](https://npmjs.com/package/@47ng/opaque-client).\n\n## Installation\n\n```\nnpm install @47ng/opaque-server\nyarn add @47ng/opaque-server\npnpm add @47ng/opaque-server\n```\n\n## Usage\n\nThis implements the [OPAQUE protocol overview](https://github.com/47ng/opaque/blob/main/docs/opaque-protocol-overview.md)\nfor a stateless server and with recommended security practices.\n\n### Setup\n\nYou'll need to create a `ServerSetup` first, which is to be treated as a secret.\n\nChanging it will invalidate your existing saved credentials, as it allows\nclients to also authenticate your server (mutual authentication).\nYou can treat it kind of like a signature private key.\n\n```ts\nimport { ServerSetup } from '@47ng/opaque-server'\nimport { hex } from '@47ng/codec'\n\nconst serverSetup = ServerSetup.serialize(new ServerSetup())\n\n// serverSetup is a byte array, you can convert it to whatever\n// string format suits your application (eg: hexadecimal here):\nconsole.info(`Generated OPAQUE server setup: ${hex.encode(serverSetup)}`)\n```\n\nWhen starting your server, assuming you obtain this serialized `ServerSetup`\nfrom a secure location (a secret management service like Hashicorp Vault,\na mounted file or an environment variable), you can hydrate it like so:\n\n```ts\nimport { ServerSetup } from '@47ng/opaque-server'\nimport { hex } from '@47ng/codec'\n\nconst serverSetup = ServerSetup.deserialize(\n  hex.decode(OPAQUE_SERIALIZED_SERVER_SETUP)\n)\n```\n\nYou will then pass this server setup to the Registration and Login handlers.\n\n### Registration (signup)\n\nOPAQUE requires two handshakes to perform a signup (technically one and a half,\nthe final response has no cryptographic use to the client):\n\nPseudo-code:\n\n```ts\nimport { HandleRegistration } from '@47ng/opaque-server'\nimport crypto from 'node:crypto'\n\n// Request handler 1 (example path: `/registration/request`)\nasync function opaqueRegistrationRequest(request, response) {\n  const registration = new HandleRegistration(serverSetup)\n  const registrationResponse = registration.start(\n    request.body.username,\n    request.body.registrationRequest\n  )\n  const nonce = crypto.randomBytes(32).toString('hex')\n  await keyValueStore.set({\n    key: nonce,\n    value: request.body.username,\n    ttl: 120, // Something short, here 2 minutes\n  })\n  response.send(registrationResponse)\n  registration.free()\n}\n\n// Request handler 2 (example path: `/registration/record`)\nasync function opaqueRegistrationRecord(request, response) {\n  const registration = new HandleRegistration(serverSetup)\n  // Do not trust client-provided usernames in the request body here:\n  // https://github.com/facebook/opaque-ke/issues/276#issuecomment-1162609521\n  const username = await keyValueStore.get({\n    key: request.body.nonce,\n  })\n  const passwordFile = registration.finish(request.body.registrationRecord)\n  await db.insert({\n    username,\n    passwordFile,\n  })\n  await keyValueStore.del({ key: request.body.nonce })\n  response.status(204).send()\n  registration.free()\n}\n```\n\n> _Note: registration doesn't perform key exchange/agreement,\n> so a login step is necessary after signup to establish a shared key._\n\n### Login\n\nOPAQUE requires two handshakes to perform a login.\n\nAt the end of the second handshake, the server will be able to use the key\nagreed upon, and the client will already have the same key, so you can start\nusing that key from the second response.\n\nPseudo-code:\n\n```ts\nimport { HandleLogin } from '@47ng/opaque-server'\n\n// Request handler 1 (example path: `/login/request`)\nasync function opaqueLoginRequest(request, response) {\n  const login = new HandleLogin(serverSetup)\n  const { passwordFile } = await db.findOne({\n    where: { username: request.body.username },\n  })\n  const loginResponse = login.start(\n    passwordFile,\n    request.body.username,\n    request.body.loginRequest\n  )\n  const loginState = login.serialize()\n  const nonce = crypto.randomBytes(32).toString('hex')\n  await keyValueStore.set({\n    key: nonce,\n    value: {\n      username: request.body.username,\n      loginState,\n    },\n    ttl: 120, // Something short, here 2 minutes\n  })\n  response.send({\n    nonce,\n    loginResponse,\n  })\n  login.free()\n}\n\n// Request handler 2 (example path: `/login/final`)\nasync function opaqueLoginFinal(request, response) {\n  const { username, loginState } = await keyValueStore.get({\n    key: request.body.nonce,\n  })\n  const login = HandleLogin.deserialize(loginState)\n  const sessionKey = login.finish(request.body.loginFinal)\n  await keyValueStore.del({ key: request.body.nonce })\n  response.setAuthCookiesFor(username)\n  response.send(...)\n  login.free()\n}\n```\n","readmeFilename":"README.md","bugs":{"url":"https://github.com/47ng/opaque/issues"},"homepage":"https://github.com/47ng/opaque#readme","_id":"@47ng/opaque-server@0.0.1-beta.13","_integrity":"sha512-qaPLIgT7KQz4RAtcfNyPmfUaAFOppcjiIQMpgyCeQ1zYARNSjlSQx7TD2HKYN9KjLiwGesEzYWZRnpbISTKrKw==","_resolved":"/tmp/0c063857910f76fa4744870504e3adaf/47ng-opaque-server-0.0.1-beta.13.tgz","_from":"file:47ng-opaque-server-0.0.1-beta.13.tgz","_nodeVersion":"18.13.0","_npmVersion":"8.19.3","dist":{"integrity":"sha512-qaPLIgT7KQz4RAtcfNyPmfUaAFOppcjiIQMpgyCeQ1zYARNSjlSQx7TD2HKYN9KjLiwGesEzYWZRnpbISTKrKw==","shasum":"263610f0fc5003027fdca75f9e1bc1129fec149c","tarball":"https://registry.npmjs.org/@47ng/opaque-server/-/opaque-server-0.0.1-beta.13.tgz","fileCount":6,"unpackedSize":259555,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIEjEw0/AeX1okoiOmcUs0f9ttLGt8/1RCb45KVC85F/jAiEA8ZxUIdUdGq1VJeJdXWpo5rZSbGmKtxO/FxYcT55EPp0="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJj2mEgACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqiOQ/5ARBLq13SPfKiYar4MOPQPa8tlb49R1Mjsfuq+EuZDicvwJOb\r\nZxXFBAfAbZd4lsrlo4y5SjZ4mNDB1O4zSMHVSgZBekJRKkY8nAF1PriPnbqy\r\nSYDbur95LC7r5Mn+cyfH0ItYS1jIdU4XK4aSyzDVVNTEbp0NxlZL+0x0v9hj\r\n+dmokr5yFkNz8EthEwUBaKfZw0nAPofW2dznWau5SnmO4ukUpBbqWM69AxZY\r\nT7rS49noAczcJLBgfrz3g+2snfETezZ8D2GJpGqPBRU2F+XO033LOlrfSE6o\r\nPQf96cf4Undm/3d95Edjm6FqTCx86yZC+wYCf3G3Zwcj0Apmq7XlmWN6lsth\r\nWUZ8bhHcryDxkyHhPrW4d6Hhe7D/n0t1NbU+JrnHuo79+Dcsb7m4BtV8Tk2s\r\nfXNbMQwrMTsbopDg/tqfp+fStilily+PAMFKG5ksdYvAlhDaOOl1RJzLiLdR\r\nSF9p35sPoqwvxJf87tqxKBM1Wl6paUH/sJl83TZztvA1ZTFcHr1KaWny0RQT\r\nkFhH5EMfYZDThaJLKqTN7Nj6ERi65sFmVbfEH6cPOOoh6CCljh5/J8qjBBI/\r\n7gZEkK+QQyRYiVlJXCzjwY7Okg7eO/azsJsMKBXdXr4ussk/sBWD5ryx1N2t\r\nl/9DZgAX2/pYFrXEAvRICgAdN40lRsPph5M=\r\n=MHvX\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"franky47","email":"accounts@francoisbest.com"},"directories":{},"maintainers":[{"name":"franky47","email":"accounts@francoisbest.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/opaque-server_0.0.1-beta.13_1675256096419_0.09223585902511933"},"_hasShrinkwrap":false},"0.0.1-beta.14":{"name":"@47ng/opaque-server","collaborators":["Marcin Lazar <marcin.lazar@pm.me>","Hawkheart","r0kk3rz","François Best <npm.opaque@francoisbest.com>"],"description":"An implementation of the OPAQUE key exchange protocol in WASM(WebAssembly)","version":"0.0.1-beta.14","repository":{"type":"git","url":"git+https://github.com/47ng/opaque.git","directory":"packages/server"},"main":"opaque-server.js","types":"opaque-server.d.ts","keywords":["cryptography","crypto","opaque","passwords","authentication","wasm","js","browser","webassembly","node","wasm-pack","bindgen","wasm-bindgen"],"type":"commonjs","license":"(Apache-2.0 OR MIT)","publishConfig":{"access":"public"},"devDependencies":{"sceau":"^1.3.0"},"scripts":{},"readme":"# `@47ng/opaque-server`\n\nThe OPAQUE key exchange protocol in WASM (WebAssembly), for Node.js.\nThis implementation is based on [facebook/opaque-ke](https://github.com/facebook/opaque-ke).\n\nBuilt as CJS for Node.js from [47ng/opaque-wasm](https://github.com/47ng/opaque-wasm/tree/fork/47ng-opaque/do-not-merge-to-upstream)\n_(a fork of [marucjmar/opaque-wasm](https://github.com/marucjmar/opaque-wasm)\nto allow server/server WASM code splitting, statelessness for the server, and uses Ristretto rather than the NIST P-256 curve)_.\n\nClient (browser) counterpart is available in [`@47ng/opaque-client`](https://npmjs.com/package/@47ng/opaque-client).\n\n## Installation\n\n```\nnpm install @47ng/opaque-server\nyarn add @47ng/opaque-server\npnpm add @47ng/opaque-server\n```\n\n## Usage\n\nThis implements the [OPAQUE protocol overview](https://github.com/47ng/opaque/blob/main/docs/opaque-protocol-overview.md)\nfor a stateless server and with recommended security practices.\n\n### Setup\n\nYou'll need to create a `ServerSetup` first, which is to be treated as a secret.\n\nChanging it will invalidate your existing saved credentials, as it allows\nclients to also authenticate your server (mutual authentication).\nYou can treat it kind of like a signature private key.\n\n```ts\nimport { ServerSetup } from '@47ng/opaque-server'\nimport { hex } from '@47ng/codec'\n\nconst serverSetup = ServerSetup.serialize(new ServerSetup())\n\n// serverSetup is a byte array, you can convert it to whatever\n// string format suits your application (eg: hexadecimal here):\nconsole.info(`Generated OPAQUE server setup: ${hex.encode(serverSetup)}`)\n```\n\nWhen starting your server, assuming you obtain this serialized `ServerSetup`\nfrom a secure location (a secret management service like Hashicorp Vault,\na mounted file or an environment variable), you can hydrate it like so:\n\n```ts\nimport { ServerSetup } from '@47ng/opaque-server'\nimport { hex } from '@47ng/codec'\n\nconst serverSetup = ServerSetup.deserialize(\n  hex.decode(OPAQUE_SERIALIZED_SERVER_SETUP)\n)\n```\n\nYou will then pass this server setup to the Registration and Login handlers.\n\n### Registration (signup)\n\nOPAQUE requires two handshakes to perform a signup (technically one and a half,\nthe final response has no cryptographic use to the client):\n\nPseudo-code:\n\n```ts\nimport { HandleRegistration } from '@47ng/opaque-server'\nimport crypto from 'node:crypto'\n\n// Request handler 1 (example path: `/registration/request`)\nasync function opaqueRegistrationRequest(request, response) {\n  const registration = new HandleRegistration(serverSetup)\n  const registrationResponse = registration.start(\n    request.body.username,\n    request.body.registrationRequest\n  )\n  const nonce = crypto.randomBytes(32).toString('hex')\n  await keyValueStore.set({\n    key: nonce,\n    value: request.body.username,\n    ttl: 120, // Something short, here 2 minutes\n  })\n  response.send(registrationResponse)\n  registration.free()\n}\n\n// Request handler 2 (example path: `/registration/record`)\nasync function opaqueRegistrationRecord(request, response) {\n  const registration = new HandleRegistration(serverSetup)\n  // Do not trust client-provided usernames in the request body here:\n  // https://github.com/facebook/opaque-ke/issues/276#issuecomment-1162609521\n  const username = await keyValueStore.get({\n    key: request.body.nonce,\n  })\n  const passwordFile = registration.finish(request.body.registrationRecord)\n  await db.insert({\n    username,\n    passwordFile,\n  })\n  await keyValueStore.del({ key: request.body.nonce })\n  response.status(204).send()\n  registration.free()\n}\n```\n\n> _Note: registration doesn't perform key exchange/agreement,\n> so a login step is necessary after signup to establish a shared key._\n\n### Login\n\nOPAQUE requires two handshakes to perform a login.\n\nAt the end of the second handshake, the server will be able to use the key\nagreed upon, and the client will already have the same key, so you can start\nusing that key from the second response.\n\nPseudo-code:\n\n```ts\nimport { HandleLogin } from '@47ng/opaque-server'\n\n// Request handler 1 (example path: `/login/request`)\nasync function opaqueLoginRequest(request, response) {\n  const login = new HandleLogin(serverSetup)\n  const { passwordFile } = await db.findOne({\n    where: { username: request.body.username },\n  })\n  const loginResponse = login.start(\n    passwordFile,\n    request.body.username,\n    request.body.loginRequest\n  )\n  const loginState = login.serialize()\n  const nonce = crypto.randomBytes(32).toString('hex')\n  await keyValueStore.set({\n    key: nonce,\n    value: {\n      username: request.body.username,\n      loginState,\n    },\n    ttl: 120, // Something short, here 2 minutes\n  })\n  response.send({\n    nonce,\n    loginResponse,\n  })\n  login.free()\n}\n\n// Request handler 2 (example path: `/login/final`)\nasync function opaqueLoginFinal(request, response) {\n  const { username, loginState } = await keyValueStore.get({\n    key: request.body.nonce,\n  })\n  const login = HandleLogin.deserialize(loginState)\n  const sessionKey = login.finish(request.body.loginFinal)\n  await keyValueStore.del({ key: request.body.nonce })\n  response.setAuthCookiesFor(username)\n  response.send(...)\n  login.free()\n}\n```\n","readmeFilename":"README.md","bugs":{"url":"https://github.com/47ng/opaque/issues"},"homepage":"https://github.com/47ng/opaque#readme","_id":"@47ng/opaque-server@0.0.1-beta.14","_integrity":"sha512-aF8xpRX9nnl34da5wvVhZlXTcrX2yZioQPp8HzecEZSC5mEdLVs1raVKJsaJ4zD6piMAtBLPpWoDcMHqroO2UQ==","_resolved":"/tmp/b3731ac6f4db80a99788180e614185c0/47ng-opaque-server-0.0.1-beta.14.tgz","_from":"file:47ng-opaque-server-0.0.1-beta.14.tgz","_nodeVersion":"18.13.0","_npmVersion":"8.19.3","dist":{"integrity":"sha512-aF8xpRX9nnl34da5wvVhZlXTcrX2yZioQPp8HzecEZSC5mEdLVs1raVKJsaJ4zD6piMAtBLPpWoDcMHqroO2UQ==","shasum":"feb0f24d7e8cc4b1aaebf536e6084b73d53db2da","tarball":"https://registry.npmjs.org/@47ng/opaque-server/-/opaque-server-0.0.1-beta.14.tgz","fileCount":6,"unpackedSize":194384,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCICrc0ld8Pr53d8IqIjY05k0fJNDGYZvcfdLXl0s2jxbmAiEAgyH0NdhUd7qwjbqBHpIjxqBt+MB6yyekAgJT0QQ5qlA="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJj2mafACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmrkHxAAllGD0H3BGG+yyg+BCgk74G+ihgiRuUPJLwOXKUS0olCEX8l0\r\nNduuaw4PFf8A9b2XeeH39tqkfZyhv6fSvKMb8XehECPyEs9K0nelLe2bIwiL\r\naLUDGKo8T7nph375ic2PnhBliRg8CHjAduPIEf83TEpZSnC7q6OVh3my7rHm\r\nfMPZifmsdXuyyPox6SI+tXqUWU+NJ+XTW8av+diOACbQThTseilVg1cQ7Ksy\r\nAZddyLT94F2iFG7LG+5r/6eSwX+xhlaTReu1yzq2ZQhGmKgbbJwJnC/PdwAp\r\nWioY3x6DPFElo441tj667enGKVsjckdXHtCB+rm/1vLWlKVcVCNCD6jikKZe\r\nKVkjSSB6yg4P7EVpoMKGzdRaEDPM0alcgADzrUgljdZU69/IefqpgqzWSOr9\r\ncK3nKgfel3t/xC6ehDaN4mjipJFmm4Y2P4riRhhHm5DqcAds1HKufwapWtq1\r\nD1D9y09Y7g2QgV0yZmRpd5euz1j7Bj6Tl4P5dQSG1ZKQIZfcb/JaKfT51cVm\r\nnimrKfQ+QOYftXtn0ObBYvx25jaOXCh4cdrv1FS4Nc/Oq7n/sKvomwZJdaCK\r\nelChmmS9Hlw6r+jH5Jg0GZv33jTPkez7nu4zZ2Oew0ngXb8kC38PUCqNeDLb\r\nVxbP1ayl7sUbt9fSlUPrSSOW8lF9RfkEc4s=\r\n=NsEG\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"franky47","email":"accounts@francoisbest.com"},"directories":{},"maintainers":[{"name":"franky47","email":"accounts@francoisbest.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/opaque-server_0.0.1-beta.14_1675257503428_0.2364244577463741"},"_hasShrinkwrap":false},"0.0.1-beta.15":{"name":"@47ng/opaque-server","collaborators":["Marcin Lazar <marcin.lazar@pm.me>","Hawkheart","r0kk3rz","François Best <npm.opaque@francoisbest.com>"],"description":"An implementation of the OPAQUE key exchange protocol in WASM(WebAssembly)","version":"0.0.1-beta.15","repository":{"type":"git","url":"git+https://github.com/47ng/opaque.git","directory":"packages/server"},"main":"opaque-server.js","types":"opaque-server.d.ts","keywords":["cryptography","crypto","opaque","passwords","authentication","wasm","js","browser","webassembly","node","wasm-pack","bindgen","wasm-bindgen"],"type":"commonjs","license":"(Apache-2.0 OR MIT)","publishConfig":{"access":"public"},"devDependencies":{"sceau":"^1.3.0"},"scripts":{},"readme":"# `@47ng/opaque-server`\n\nThe OPAQUE key exchange protocol in WASM (WebAssembly), for Node.js.\nThis implementation is based on [facebook/opaque-ke](https://github.com/facebook/opaque-ke).\n\nBuilt as CJS for Node.js from [47ng/opaque-wasm](https://github.com/47ng/opaque-wasm/tree/fork/47ng-opaque/do-not-merge-to-upstream)\n_(a fork of [marucjmar/opaque-wasm](https://github.com/marucjmar/opaque-wasm)\nto allow server/server WASM code splitting, statelessness for the server, and uses Ristretto rather than the NIST P-256 curve)_.\n\nClient (browser) counterpart is available in [`@47ng/opaque-client`](https://npmjs.com/package/@47ng/opaque-client).\n\n## Installation\n\n```\nnpm install @47ng/opaque-server\nyarn add @47ng/opaque-server\npnpm add @47ng/opaque-server\n```\n\n## Usage\n\nThis implements the [OPAQUE protocol overview](https://github.com/47ng/opaque/blob/main/docs/opaque-protocol-overview.md)\nfor a stateless server and with recommended security practices.\n\n### Setup\n\nYou'll need to create a `ServerSetup` first, which is to be treated as a secret.\n\nChanging it will invalidate your existing saved credentials, as it allows\nclients to also authenticate your server (mutual authentication).\nYou can treat it kind of like a signature private key.\n\n```ts\nimport { ServerSetup } from '@47ng/opaque-server'\nimport { hex } from '@47ng/codec'\n\nconst serverSetup = ServerSetup.serialize(new ServerSetup())\n\n// serverSetup is a byte array, you can convert it to whatever\n// string format suits your application (eg: hexadecimal here):\nconsole.info(`Generated OPAQUE server setup: ${hex.encode(serverSetup)}`)\n```\n\nWhen starting your server, assuming you obtain this serialized `ServerSetup`\nfrom a secure location (a secret management service like Hashicorp Vault,\na mounted file or an environment variable), you can hydrate it like so:\n\n```ts\nimport { ServerSetup } from '@47ng/opaque-server'\nimport { hex } from '@47ng/codec'\n\nconst serverSetup = ServerSetup.deserialize(\n  hex.decode(OPAQUE_SERIALIZED_SERVER_SETUP)\n)\n```\n\nYou will then pass this server setup to the Registration and Login handlers.\n\n### Registration (signup)\n\nOPAQUE requires two handshakes to perform a signup (technically one and a half,\nthe final response has no cryptographic use to the client):\n\nPseudo-code:\n\n```ts\nimport { HandleRegistration } from '@47ng/opaque-server'\nimport crypto from 'node:crypto'\n\n// Request handler 1 (example path: `/registration/request`)\nasync function opaqueRegistrationRequest(request, response) {\n  const registration = new HandleRegistration(serverSetup)\n  const registrationResponse = registration.start(\n    request.body.username,\n    request.body.registrationRequest\n  )\n  const nonce = crypto.randomBytes(32).toString('hex')\n  await keyValueStore.set({\n    key: nonce,\n    value: request.body.username,\n    ttl: 120, // Something short, here 2 minutes\n  })\n  response.send(registrationResponse)\n  registration.free()\n}\n\n// Request handler 2 (example path: `/registration/record`)\nasync function opaqueRegistrationRecord(request, response) {\n  const registration = new HandleRegistration(serverSetup)\n  // Do not trust client-provided usernames in the request body here:\n  // https://github.com/facebook/opaque-ke/issues/276#issuecomment-1162609521\n  const username = await keyValueStore.get({\n    key: request.body.nonce,\n  })\n  const passwordFile = registration.finish(request.body.registrationRecord)\n  await db.insert({\n    username,\n    passwordFile,\n  })\n  await keyValueStore.del({ key: request.body.nonce })\n  response.status(204).send()\n  registration.free()\n}\n```\n\n> _Note: registration doesn't perform key exchange/agreement,\n> so a login step is necessary after signup to establish a shared key._\n\n### Login\n\nOPAQUE requires two handshakes to perform a login.\n\nAt the end of the second handshake, the server will be able to use the key\nagreed upon, and the client will already have the same key, so you can start\nusing that key from the second response.\n\nPseudo-code:\n\n```ts\nimport { HandleLogin } from '@47ng/opaque-server'\n\n// Request handler 1 (example path: `/login/request`)\nasync function opaqueLoginRequest(request, response) {\n  const login = new HandleLogin(serverSetup)\n  const { passwordFile } = await db.findOne({\n    where: { username: request.body.username },\n  })\n  const loginResponse = login.start(\n    passwordFile,\n    request.body.username,\n    request.body.loginRequest\n  )\n  const loginState = login.serialize()\n  const nonce = crypto.randomBytes(32).toString('hex')\n  await keyValueStore.set({\n    key: nonce,\n    value: {\n      username: request.body.username,\n      loginState,\n    },\n    ttl: 120, // Something short, here 2 minutes\n  })\n  response.send({\n    nonce,\n    loginResponse,\n  })\n  login.free()\n}\n\n// Request handler 2 (example path: `/login/final`)\nasync function opaqueLoginFinal(request, response) {\n  const { username, loginState } = await keyValueStore.get({\n    key: request.body.nonce,\n  })\n  const login = HandleLogin.deserialize(loginState)\n  const sessionKey = login.finish(request.body.loginFinal)\n  await keyValueStore.del({ key: request.body.nonce })\n  response.setAuthCookiesFor(username)\n  response.send(...)\n  login.free()\n}\n```\n","readmeFilename":"README.md","bugs":{"url":"https://github.com/47ng/opaque/issues"},"homepage":"https://github.com/47ng/opaque#readme","_id":"@47ng/opaque-server@0.0.1-beta.15","_integrity":"sha512-JjfEa5kLZCBNRMNtclTGGorHCBuzFnnKwLf9+Ily0et5Flci6JuPUe6Veiss/r65mlk5OCBWvbwOYSUk3CFEhg==","_resolved":"/tmp/f391bfef370793476a9c587349f9a86c/47ng-opaque-server-0.0.1-beta.15.tgz","_from":"file:47ng-opaque-server-0.0.1-beta.15.tgz","_nodeVersion":"18.13.0","_npmVersion":"8.19.3","dist":{"integrity":"sha512-JjfEa5kLZCBNRMNtclTGGorHCBuzFnnKwLf9+Ily0et5Flci6JuPUe6Veiss/r65mlk5OCBWvbwOYSUk3CFEhg==","shasum":"fefd4378f42f815dd52002ebc106dbc164d855ce","tarball":"https://registry.npmjs.org/@47ng/opaque-server/-/opaque-server-0.0.1-beta.15.tgz","fileCount":6,"unpackedSize":193999,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDP4b3uCZqCrUBw18Wf2da65pO3ThcNh5oeaCUe8hWk/wIgMhwliY7j6GpOFmsPpnEpuIcToSfS41kx9rEt7+PdCTI="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJj27gmACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqDZA/+MChNqa9durE41hyWhDZgZTPc2b3VXEm5sC4vufd47qFm1LRs\r\nkbQwRfCst3udBKv1zTrc85UDCijxwXcYJ2nwbkYPRE8aIqN3XvQ+m+Yw4C61\r\nmAXdkaUL5mSjkgkpgEZKI1AYbVuQjMQGqHLe5yDl3twlW2sE94Xt2X8IO+E0\r\nw+vEFji1yzlo7XvLwAUmG4bLBBf2/kZ/zX/tRFPbPxmNtUXv/TdeWjkj1K/Q\r\nsBZsNJlxttDDZzm1nhQHzJ6emCinNgKFXwOjwzlQD5qHF373/wNUWrgP9HKS\r\nx9pD2NAWhM541Kxl2LGdaEHWlHBtstvMLuKpRZpdPZ7DhEyg02FiZppouScX\r\n599LXG8WllR61oE+W+tZfjUIAX43HSvEkOy8G07VFNr5nLStci3BHQA/ycbV\r\nVS5w0+ftAndBjBoDkVab1RSCYXtew2Epi24jiII0PBW6O4G9nJT0Yj8Cn9jJ\r\nY/Qz7fgpHsYuuZlD2me7ZCUfN17NAtSuD5qCHY30MYIvNfNWtXEyOdGkikfy\r\nlzUm/aFIxo5iDuIKSSXu+T3kqP0BEX2ed1hwwjjVBGEt8qqAUA7HKV+l8xBO\r\n+i6ee/CkcpEim47r2/s2YtN2mNG2u61KjBek/j3BjiJPdtG53XR3dRqnz5L8\r\nS03X0+PJvipiO2/nyznMC37T8hFEqaJkKUo=\r\n=ejLo\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"franky47","email":"accounts@francoisbest.com"},"directories":{},"maintainers":[{"name":"franky47","email":"accounts@francoisbest.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/opaque-server_0.0.1-beta.15_1675343910689_0.15264104402041112"},"_hasShrinkwrap":false},"0.0.1-beta.16":{"name":"@47ng/opaque-server","collaborators":["Marcin Lazar <marcin.lazar@pm.me>","Hawkheart","r0kk3rz","François Best <npm.opaque@francoisbest.com>"],"description":"An implementation of the OPAQUE key exchange protocol in WASM(WebAssembly)","version":"0.0.1-beta.16","repository":{"type":"git","url":"git+https://github.com/47ng/opaque.git","directory":"packages/server"},"main":"opaque-server.js","types":"opaque-server.d.ts","keywords":["cryptography","crypto","opaque","passwords","authentication","wasm","js","browser","webassembly","node","wasm-pack","bindgen","wasm-bindgen"],"type":"commonjs","license":"(Apache-2.0 OR MIT)","publishConfig":{"access":"public"},"devDependencies":{"sceau":"^1.3.0"},"scripts":{},"readme":"# `@47ng/opaque-server`\n\nThe OPAQUE key exchange protocol in WASM (WebAssembly), for Node.js.\nThis implementation is based on [facebook/opaque-ke](https://github.com/facebook/opaque-ke).\n\nBuilt as CJS for Node.js from [47ng/opaque-wasm](https://github.com/47ng/opaque-wasm/tree/fork/47ng-opaque/do-not-merge-to-upstream)\n_(a fork of [marucjmar/opaque-wasm](https://github.com/marucjmar/opaque-wasm)\nto allow server/server WASM code splitting, statelessness for the server, and uses Ristretto rather than the NIST P-256 curve)_.\n\nClient (browser) counterpart is available in [`@47ng/opaque-client`](https://npmjs.com/package/@47ng/opaque-client).\n\n## Installation\n\n```\nnpm install @47ng/opaque-server\nyarn add @47ng/opaque-server\npnpm add @47ng/opaque-server\n```\n\n## Usage\n\nThis implements the [OPAQUE protocol overview](https://github.com/47ng/opaque/blob/main/docs/opaque-protocol-overview.md)\nfor a stateless server and with recommended security practices.\n\n### Setup\n\nYou'll need to create a `ServerSetup` first, which is to be treated as a secret.\n\nChanging it will invalidate your existing saved credentials, as it allows\nclients to also authenticate your server (mutual authentication).\nYou can treat it kind of like a signature private key.\n\n```ts\nimport { ServerSetup } from '@47ng/opaque-server'\nimport { hex } from '@47ng/codec'\n\nconst serverSetup = ServerSetup.serialize(new ServerSetup())\n\n// serverSetup is a byte array, you can convert it to whatever\n// string format suits your application (eg: hexadecimal here):\nconsole.info(`Generated OPAQUE server setup: ${hex.encode(serverSetup)}`)\n```\n\nWhen starting your server, assuming you obtain this serialized `ServerSetup`\nfrom a secure location (a secret management service like Hashicorp Vault,\na mounted file or an environment variable), you can hydrate it like so:\n\n```ts\nimport { ServerSetup } from '@47ng/opaque-server'\nimport { hex } from '@47ng/codec'\n\nconst serverSetup = ServerSetup.deserialize(\n  hex.decode(OPAQUE_SERIALIZED_SERVER_SETUP)\n)\n```\n\nYou will then pass this server setup to the Registration and Login handlers.\n\n### Registration (signup)\n\nOPAQUE requires two handshakes to perform a signup (technically one and a half,\nthe final response has no cryptographic use to the client):\n\nPseudo-code:\n\n```ts\nimport { HandleRegistration } from '@47ng/opaque-server'\nimport crypto from 'node:crypto'\n\n// Request handler 1 (example path: `/registration/request`)\nasync function opaqueRegistrationRequest(request, response) {\n  const registration = new HandleRegistration(serverSetup)\n  const registrationResponse = registration.start(\n    request.body.username,\n    request.body.registrationRequest\n  )\n  const nonce = crypto.randomBytes(32).toString('hex')\n  await keyValueStore.set({\n    key: nonce,\n    value: request.body.username,\n    ttl: 120, // Something short, here 2 minutes\n  })\n  response.send(registrationResponse)\n  registration.free()\n}\n\n// Request handler 2 (example path: `/registration/record`)\nasync function opaqueRegistrationRecord(request, response) {\n  const registration = new HandleRegistration(serverSetup)\n  // Do not trust client-provided usernames in the request body here:\n  // https://github.com/facebook/opaque-ke/issues/276#issuecomment-1162609521\n  const username = await keyValueStore.get({\n    key: request.body.nonce,\n  })\n  const passwordFile = registration.finish(request.body.registrationRecord)\n  await db.insert({\n    username,\n    passwordFile,\n  })\n  await keyValueStore.del({ key: request.body.nonce })\n  response.status(204).send()\n  // Note: there is no need to free the `registration` object here,\n  // it's been taken care of by the call to `finish`.\n}\n```\n\n> _Note: registration doesn't perform key exchange/agreement,\n> so a login step is necessary after signup to establish a shared key._\n\n### Login\n\nOPAQUE requires two handshakes to perform a login.\n\nAt the end of the second handshake, the server will be able to use the key\nagreed upon, and the client will already have the same key, so you can start\nusing that key from the second response.\n\nPseudo-code:\n\n```ts\nimport { HandleLogin } from '@47ng/opaque-server'\n\n// Request handler 1 (example path: `/login/request`)\nasync function opaqueLoginRequest(request, response) {\n  const login = new HandleLogin(serverSetup)\n  const { passwordFile } = await db.findOne({\n    where: { username: request.body.username },\n  })\n  const loginResponse = login.start(\n    passwordFile,\n    request.body.username,\n    request.body.loginRequest\n  )\n  const loginState = login.serialize()\n  const nonce = crypto.randomBytes(32).toString('hex')\n  await keyValueStore.set({\n    key: nonce,\n    value: {\n      username: request.body.username,\n      loginState,\n    },\n    ttl: 120, // Something short, here 2 minutes\n  })\n  response.send({\n    nonce,\n    loginResponse,\n  })\n  login.free()\n}\n\n// Request handler 2 (example path: `/login/final`)\nasync function opaqueLoginFinal(request, response) {\n  const { username, loginState } = await keyValueStore.get({\n    key: request.body.nonce,\n  })\n  const login = HandleLogin.deserialize(loginState)\n  const sessionKey = login.finish(request.body.loginFinal)\n  await keyValueStore.del({ key: request.body.nonce })\n  response.setAuthCookiesFor(username)\n  response.send(...)\n  login.free()\n}\n```\n","readmeFilename":"README.md","bugs":{"url":"https://github.com/47ng/opaque/issues"},"homepage":"https://github.com/47ng/opaque#readme","_id":"@47ng/opaque-server@0.0.1-beta.16","_integrity":"sha512-oC2kEa75XyxrZMmiEPzxCcJTDi5ncuw1hjjp/3wPWvERpNrqL0Y/qn4zNUCT4olpma6/Bidspgz9UfM/k6wnWg==","_resolved":"/tmp/f7fcca13ed4405949514f30143898c07/47ng-opaque-server-0.0.1-beta.16.tgz","_from":"file:47ng-opaque-server-0.0.1-beta.16.tgz","_nodeVersion":"18.13.0","_npmVersion":"8.19.3","dist":{"integrity":"sha512-oC2kEa75XyxrZMmiEPzxCcJTDi5ncuw1hjjp/3wPWvERpNrqL0Y/qn4zNUCT4olpma6/Bidspgz9UfM/k6wnWg==","shasum":"7c0dfbe9a41300ae6c51ab3cd9e208702c5e878c","tarball":"https://registry.npmjs.org/@47ng/opaque-server/-/opaque-server-0.0.1-beta.16.tgz","fileCount":6,"unpackedSize":194099,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDq390Udxbh3625aIPpFpDL2gwdgriE2QycdCyu9XHBAwIhAJZnLp42xBYY2IjeC4LvXe4O5knRRkJSvb6l8JbkNUcS"}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJj3PKRACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmrdfQ//aXjMMgg+KOjOkbP7Zy/viW9P461Eyg+F+wU+mMvUwT3JcqvF\r\nRIJX4Kxn32ZzteshHVi8peCAuZPu+E67oKaX0zVX3sVtLMQR7sd6PT+ygKt8\r\nm2h2kVtCByOdpi3XUmH7IYYHUQmyUdk2BWZgNdBDYTkHZlkKkHHsiOZBy1Of\r\nZecfef13BkQxPLqaZkCO8ujnEfiXQcjX7E6PVh70EyOxRHvfHvHy5PMXRkeq\r\nl5JY9VzWoQUZyzsYVb8tI+zOKqEOH+KMXN8VBgL5c2XUXBstxHz2wjKbvtKp\r\nCmojASP06d6RHgnyO7RLTev9QlfjV6mqHEw3P/i54YR11TPGqdHhdmOR3LUB\r\nVBKEl5ZsrxeJrtFby/pKlUIgQqKTTwdFl4xT8TlzH1K0CaQdH+hjI6FwskaA\r\noyTSIwaduWV0cTorp2Gc83R3k8EmJ51Q7xhdthpIKdtOuafrlvkWVSpO3ERr\r\nCSdriy4IJvBMab/TMqUE1eYJDd17tKKe1xj17tEbHu2X/qRS//HHLImUs19U\r\neFzXVeyie9bEgAb3zGqCazgT6R61zco8/118cedSKoHxjGSFOyzKznTIPLnz\r\nFhYxHWs5P/JCQ6botPlM9JmPZovtyULmqvUgeWaRW4Q0RUgdc/f8omMiAp9f\r\nI4Yn6SCNaDU+saTdaP0gNw3UixZOsOZpSJA=\r\n=B/hc\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"franky47","email":"accounts@francoisbest.com"},"directories":{},"maintainers":[{"name":"franky47","email":"accounts@francoisbest.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/opaque-server_0.0.1-beta.16_1675424400886_0.40261793070421303"},"_hasShrinkwrap":false},"1.0.0":{"name":"@47ng/opaque-server","collaborators":["Marcin Lazar <marcin.lazar@pm.me>","Hawkheart","r0kk3rz","François Best <npm.opaque@francoisbest.com>"],"description":"An implementation of the OPAQUE key exchange protocol in WASM(WebAssembly)","version":"1.0.0","repository":{"type":"git","url":"git+https://github.com/47ng/opaque.git","directory":"packages/server"},"main":"opaque-server.js","types":"opaque-server.d.ts","keywords":["cryptography","crypto","opaque","passwords","authentication","wasm","js","browser","webassembly","node","wasm-pack","bindgen","wasm-bindgen"],"type":"commonjs","license":"(Apache-2.0 OR MIT)","publishConfig":{"access":"public"},"devDependencies":{"sceau":"^1.3.0"},"scripts":{},"bugs":{"url":"https://github.com/47ng/opaque/issues"},"homepage":"https://github.com/47ng/opaque#readme","_id":"@47ng/opaque-server@1.0.0","_integrity":"sha512-r1jj0L2ZcXFGZC67zoYd8bLGpgrv6DBwSzTBKfdgL0EUCdlExq0Aj8cddJ1GtVt+KkX68oG37oYAVDtrrj8xtQ==","_resolved":"/tmp/2721e68165d5917910eab14be208c83f/47ng-opaque-server-1.0.0.tgz","_from":"file:47ng-opaque-server-1.0.0.tgz","_nodeVersion":"18.13.0","_npmVersion":"8.19.3","dist":{"integrity":"sha512-r1jj0L2ZcXFGZC67zoYd8bLGpgrv6DBwSzTBKfdgL0EUCdlExq0Aj8cddJ1GtVt+KkX68oG37oYAVDtrrj8xtQ==","shasum":"53bef255a703d35f983c475bceffa6f2bd0846aa","tarball":"https://registry.npmjs.org/@47ng/opaque-server/-/opaque-server-1.0.0.tgz","fileCount":6,"unpackedSize":194191,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIHgMN/eaPda5R8tJKsuhAfUN/u+0YG4uDUQb8KP/qJ8pAiATS1wextgUhFWoaiZ3hnICUlaDy79CxwZ93cHaV9iupA=="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJj3P7EACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmr9yg/+N2cEQLwGIeDyr7Vh99DVChao8bt/8xQScZQmzPrzzBDBe9ak\r\nNew3R3dQ6GtkFj0i0YmzCRujm0+/3g8PFPSfh3ie5SnaGjdNo9yNN4Tq1cMv\r\nznZg2x9q2ZiLnly44IaUVwLBkFqllOowLs1MUqewHSkqiPkwTkmgDtybmGax\r\n6Yoiy3VvUpVV5+NmqouOMIZddK3L7v4qNriQdyKBl0wktVvolT3IiSH/xICH\r\nfKUeIvCV2blbxqjnfIga12IdKmlRo6C9auTlSqlFD9QOpR672aFUUuxzjsbT\r\nCx07Fx/Uy7z8J/NiMpdAfHNSXEwYSSx60Np8BI4kzBo27p3HkM3igIvbx08p\r\nDjVWzjS+WRyg+30leekscrXghFe6WM3zKjH1/EJz2z6zILPcOUS5f32mHNi2\r\nwJY5hg1Nez4hF+IGnV5/S+aS0YIsRdV5ZtqYCVeKx09K7leIUdtJj4amQTS0\r\n4E3ixm2rnwUmPIwpf41If+PKDYf8SHbDa+ZKpHjMowEiQfnVPG0AVhJn/jUg\r\nvaNimMU3/Noe4moMDEpNnpgMtcpnmUgvFOO9uh3bhEL0cbr5k8SLoA/BxeAr\r\nJAmfLeZC6CgiN3BvU5f/VsIp37XFw1j6jexrsNyzPp68VfyInT68cpsQQ1u0\r\ngLQM4BvEDkNdHEY6T9+fcziPz9FMF01o5yU=\r\n=Uj2b\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"franky47","email":"accounts@francoisbest.com"},"directories":{},"maintainers":[{"name":"franky47","email":"accounts@francoisbest.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/opaque-server_1.0.0_1675427524497_0.2710340942384346"},"_hasShrinkwrap":false},"2.1.0":{"name":"@47ng/opaque-server","collaborators":["Marcin Lazar <marcin.lazar@pm.me>","Hawkheart","r0kk3rz","François Best <npm.opaque@francoisbest.com>"],"description":"An implementation of the OPAQUE key exchange protocol in WASM(WebAssembly)","version":"2.1.0","repository":{"type":"git","url":"git+https://github.com/47ng/opaque.git","directory":"packages/server"},"main":"opaque-server.js","types":"opaque-server.d.ts","keywords":["cryptography","crypto","opaque","passwords","authentication","wasm","js","browser","webassembly","node","wasm-pack","bindgen","wasm-bindgen"],"type":"commonjs","license":"(Apache-2.0 OR MIT)","publishConfig":{"access":"public"},"devDependencies":{"sceau":"^1.3.0"},"scripts":{},"bugs":{"url":"https://github.com/47ng/opaque/issues"},"homepage":"https://github.com/47ng/opaque#readme","_id":"@47ng/opaque-server@2.1.0","_integrity":"sha512-xJogcevnuT0NBmnE697aic9qzi3UQbIkEn67Ib0h/hRdR6WRBb9mhl/fEtLhOCeFJedOyv0jdrbRxTPyavxECg==","_resolved":"/tmp/ef88f25e2a9514845106bac1afafaf9a/47ng-opaque-server-2.1.0.tgz","_from":"file:47ng-opaque-server-2.1.0.tgz","_nodeVersion":"18.13.0","_npmVersion":"8.19.3","dist":{"integrity":"sha512-xJogcevnuT0NBmnE697aic9qzi3UQbIkEn67Ib0h/hRdR6WRBb9mhl/fEtLhOCeFJedOyv0jdrbRxTPyavxECg==","shasum":"422a6abf73da218894c866f693b3300ddd9ffcd9","tarball":"https://registry.npmjs.org/@47ng/opaque-server/-/opaque-server-2.1.0.tgz","fileCount":6,"unpackedSize":194114,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIDGPUIsoFzKzo3njzTvUURgrap1ISUGF0/xfX5Mt9WwSAiARmIRkU4tgHKq0ChQn8PPBcTGUw9FNCJIUXTAnPP4TgA=="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJj3QFVACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmoGfBAAh2L7dVK0vyVoLQgrWWpLMgcZ9VptiHEk8NM0Gz3hgfhcAmm0\r\nNHMIJzE6KDPNjnHYpnF0w7w8Y0M9CRNcCwVYjGf3bX/vHfsciMwK+2AxjKKA\r\nROpxaqVJnvBktg1lvuZNPFw4aI/k5FemdxvJY33l7v/uBWdgqB8wdYz2EAZ9\r\nAt52nWHnGqyvcZp6canIb5LXQLPgVDz4eo9idjk/kFhYNiQS2ONuhi77Vlcs\r\nMVmpMVt/yu8jkX98AY2EYR8uGYB5gSEGv7BXaUam7wixbVvOEm2aVrZUaVEH\r\nrjFA8WvbExoRLrZuwAJ9fWGbQLyUmS39fsVPJq88fFcjU9ZQnIwZz/oky/iO\r\nxmpw8mqgogtcGS9MEPkyxbnG0l3ULe7AG5KSp7T8gh82DapSuHBYvUtfkcSD\r\ndQtMOTw1iK2ctHqAFQYfqJWdMWjENiA9B6CiuC+NONJMkk+N4lWRoLgE8zbO\r\nI8CAh1Eg3pO0NDs1LN1Si1R2nwT1b4bZm8OBmYI2DqDHa1zQWIB++eT1eNtE\r\nO1VsEmMI1gloJoZDs1HB14aYA9w1sed8JPPhJWS0AnFjI+jlpDiqJjBDY4Xm\r\n8+/s9nG6R++/Lny8YTgt6tVrEz00B3rQ8RSJ3agURahngaYUaFJJ/+Kb5VQ6\r\nCfCVlETLWLR46kZXjtw9imzLUnVAYn4t7yo=\r\n=NGmN\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"franky47","email":"accounts@francoisbest.com"},"directories":{},"maintainers":[{"name":"franky47","email":"accounts@francoisbest.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/opaque-server_2.1.0_1675428181432_0.2593708571508102"},"_hasShrinkwrap":false},"2.1.1":{"name":"@47ng/opaque-server","collaborators":["Marcin Lazar <marcin.lazar@pm.me>","Hawkheart","r0kk3rz","François Best <npm.opaque@francoisbest.com>"],"description":"An implementation of the OPAQUE key exchange protocol in WASM(WebAssembly)","version":"2.1.1","repository":{"type":"git","url":"git+https://github.com/47ng/opaque.git","directory":"packages/server"},"main":"opaque-server.js","types":"opaque-server.d.ts","keywords":["cryptography","crypto","opaque","passwords","authentication","wasm","js","browser","webassembly","node","wasm-pack","bindgen","wasm-bindgen"],"type":"commonjs","license":"(Apache-2.0 OR MIT)","publishConfig":{"access":"public"},"devDependencies":{"sceau":"^1.3.0"},"scripts":{},"bugs":{"url":"https://github.com/47ng/opaque/issues"},"homepage":"https://github.com/47ng/opaque#readme","_id":"@47ng/opaque-server@2.1.1","_integrity":"sha512-1FpNOCA/2SCTb7TmPf4byRH6CObeW+MZdHUuF174gJeUhSaH6tp96Nv3qsADJ8y6aQFtW0LEJntxuhp1omTTQw==","_resolved":"/tmp/9f49764bee81b64cd60b368d259133bc/47ng-opaque-server-2.1.1.tgz","_from":"file:47ng-opaque-server-2.1.1.tgz","_nodeVersion":"18.14.0","_npmVersion":"9.3.1","dist":{"integrity":"sha512-1FpNOCA/2SCTb7TmPf4byRH6CObeW+MZdHUuF174gJeUhSaH6tp96Nv3qsADJ8y6aQFtW0LEJntxuhp1omTTQw==","shasum":"204dcd15688a4f5ecc58d4c30fd0466770c929b9","tarball":"https://registry.npmjs.org/@47ng/opaque-server/-/opaque-server-2.1.1.tgz","fileCount":6,"unpackedSize":194479,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIDuQHEF7I9fcKSwtAJdKlLA9Kj9+bjefdT0T9Z5ioBKYAiEAphsJUNfMZtSj3k+W/stkTe4pCdlbwX9+BAjaAnG3Crw="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJj7jQOACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqQ+g//UNre4y6j9NpkGSc+Lwta3Hpjc4N2Ug0sgL1O/KVzxzYVn5z7\r\nv18o5mJ26KAvCE2xJeEJMgAnyH2C4NeyT0KhhFTmuROj1Gbg5Jp3M46WMtuq\r\nvluhDz344fcQoxqLm0YVQKzMyt/RhKBE2ItjslvQTrRKUxOkiiQtyXlQ5WZe\r\nvxB7nJ8vvJEekR5uovqDAtPZ/HZ9Gxynq6mQcPGFPZxL+gU4ztbHT22nSufS\r\nwof1NQeAYxRLoI19XY80Y6WXlbDCWzv0XmgA/PXe2xYwINHYbki7GxZGRkzG\r\ntecMbOOlxkWj+9jy/nvEtiumWGTO1PfZA0715moJvcxYyM/ODFdIuIbXeXc/\r\n3gpT528rePukTrZldC4l+C0xmISILj14UvD8cHs3dlX85UcWsVx1eBuw/MAw\r\n0vR9894uXHE65/mMkPLXRpywToO4fSZ3ViRa9dyd27G7RYVQZGaLYf1pCAXT\r\nqooDnEBZVe+C3MZoHJP+dNqfH5LCVPnTT4k58mWCTjkuEejm+PzL0bJevbtX\r\np78WkPqKQBCoeU9HgcrwjHDl1QxksAcO6vC8rTqXZa6tceAZ+A/cATI+j7Sg\r\nQKj8Hqwir8k5o25IPRdbyCoYcHBtxo1gksoMsnJVA3T3OGziGzY6l5C8YDMp\r\nhkT3iTYjpFEU1ASmZTX+DJqaPN63NOIAKyg=\r\n=g3ya\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"franky47","email":"accounts@francoisbest.com"},"directories":{},"maintainers":[{"name":"franky47","email":"accounts@francoisbest.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/opaque-server_2.1.1_1676555278280_0.8300534339078898"},"_hasShrinkwrap":false},"2.1.2":{"name":"@47ng/opaque-server","collaborators":["Marcin Lazar <marcin.lazar@pm.me>","Hawkheart","r0kk3rz","François Best <npm.opaque@francoisbest.com>"],"description":"An implementation of the OPAQUE key exchange protocol in WASM(WebAssembly)","version":"2.1.2","repository":{"type":"git","url":"git+https://github.com/47ng/opaque.git","directory":"packages/server"},"main":"opaque-server.js","types":"opaque-server.d.ts","keywords":["cryptography","crypto","opaque","passwords","authentication","wasm","js","browser","webassembly","node","wasm-pack","bindgen","wasm-bindgen"],"type":"commonjs","license":"(Apache-2.0 OR MIT)","publishConfig":{"access":"public"},"devDependencies":{"sceau":"^1.3.0"},"scripts":{},"bugs":{"url":"https://github.com/47ng/opaque/issues"},"homepage":"https://github.com/47ng/opaque#readme","_id":"@47ng/opaque-server@2.1.2","_integrity":"sha512-0bKRG6Rl+uhdUiDwQxihh0dw4abpn4oKSx/wdWMzTFBHDWNRFMVF+KGKgwc4SRoAghoeug5uWaE+7WUrec87Fg==","_resolved":"/tmp/38bdd2a76bcc5d5077e450329edb496a/47ng-opaque-server-2.1.2.tgz","_from":"file:47ng-opaque-server-2.1.2.tgz","_nodeVersion":"18.14.0","_npmVersion":"9.3.1","dist":{"integrity":"sha512-0bKRG6Rl+uhdUiDwQxihh0dw4abpn4oKSx/wdWMzTFBHDWNRFMVF+KGKgwc4SRoAghoeug5uWaE+7WUrec87Fg==","shasum":"eaa607e8db884437bdc60fe74e5eb5c892408cbd","tarball":"https://registry.npmjs.org/@47ng/opaque-server/-/opaque-server-2.1.2.tgz","fileCount":6,"unpackedSize":194459,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIArJUx7Zknmu3s+xOALzhftuYXHsQA2DwA/tmGgJKGUbAiEAw/qVbvTZiJ9oI/eUGItpqKySr6B97Dxm8Vo8r70Pmr0="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJj9UoaACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmpPlw/+LBivkrcOD1rqZuOTmGaxLIKB680q1XZLx5bw7oqK3LWLSA1a\r\nhgYQ6CP+jKhn5ow7SyBgrojoWhSLK2PjWovTboFQr0+GMkBM6wTH8Qh7X3FF\r\nTbp7gElii3ygxr+ahmo/l50TqpnHkb+D3UmFjPHXJzgI0XZXgfDI/gJt4Yyb\r\nDmmGV8blkzJTq/uJ501jN4RMhk5/ZkRt426rB/iMyoVAW4MCr5RPsKFdHLYD\r\n5cRYNbCG+98m/cZjnik7hu+tgdlNB79QChpZa+tVd7W3YxujuHkezX5wGFqK\r\nQHFjUtE0M4OqWr1b6weMM3kwhjBFYfkqqowSJb54WiwodNMsscUvbpdN7vrJ\r\nVTEXmwgWg35JsBm/385sbAp0b50elGWAUpNGSYLly7tpOMRPOXw/irVyQOws\r\nCDh2ak5MsTGiGV7lp3ZiZ6L1pI+iXh1F8jdDINjbNDYx4fuzQTsipKNP19kA\r\nCuR+1SAOaK8dgBlvzUU3ruQDR0Px4nu/w9OEpEQhscizBfML44kAUMUaj9oc\r\nQT9lO2E4kv/EEkYM/wsz7KhsG6I1WNQCondutk1rK5V60Hgx9WuMh2HvB/uh\r\n5zOnrCtf0erpDvlfbQQ+gbgtMPs4h22CB+U7QHPn+onBC2r2wsmPSHfpc62e\r\nU8kF43hoaWz8hVlN9KAFMK+YKnV/JCCi3OM=\r\n=tdO6\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"franky47","email":"accounts@francoisbest.com"},"directories":{},"maintainers":[{"name":"franky47","email":"accounts@francoisbest.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/opaque-server_2.1.2_1677019673804_0.8921040196309862"},"_hasShrinkwrap":false},"2.1.3":{"name":"@47ng/opaque-server","collaborators":["Marcin Lazar <marcin.lazar@pm.me>","Hawkheart","r0kk3rz","François Best <npm.opaque@francoisbest.com>"],"description":"An implementation of the OPAQUE key exchange protocol in WASM(WebAssembly)","version":"2.1.3","repository":{"type":"git","url":"git+https://github.com/47ng/opaque.git","directory":"packages/server"},"main":"opaque-server.js","types":"opaque-server.d.ts","keywords":["cryptography","crypto","opaque","passwords","authentication","wasm","js","browser","webassembly","node","wasm-pack","bindgen","wasm-bindgen"],"type":"commonjs","license":"(Apache-2.0 OR MIT)","publishConfig":{"access":"public"},"devDependencies":{"sceau":"^1.3.0"},"scripts":{},"bugs":{"url":"https://github.com/47ng/opaque/issues"},"homepage":"https://github.com/47ng/opaque#readme","_id":"@47ng/opaque-server@2.1.3","_integrity":"sha512-MHA8o4tE1/1jooYF2g3gA3J7p2yWcmZyEsYj0HC+W4WsgXkKvwQSTNDbBu90CUHixBZgusaiHhOqfSaYRMAaCw==","_resolved":"/tmp/addb828bf7f11dc4a53e5fd9a99a99ea/47ng-opaque-server-2.1.3.tgz","_from":"file:47ng-opaque-server-2.1.3.tgz","_nodeVersion":"18.14.0","_npmVersion":"9.3.1","dist":{"integrity":"sha512-MHA8o4tE1/1jooYF2g3gA3J7p2yWcmZyEsYj0HC+W4WsgXkKvwQSTNDbBu90CUHixBZgusaiHhOqfSaYRMAaCw==","shasum":"604175b6f536b54a4d2e03f65b7be3073b13257d","tarball":"https://registry.npmjs.org/@47ng/opaque-server/-/opaque-server-2.1.3.tgz","fileCount":6,"unpackedSize":194459,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIGC6VFNxSVoc1aVQ3ObQG2qdl26yoh7gY3SnLiZYojpvAiEAtsgErpIKoOx55G6YlyoPWrEA1wxvHA+zfdRcQbd7AdQ="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJj9UvAACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqA8Q/+KddG8Xgl5SoJwp5hOxrhrqNQ8/XbJXCbiaHx6MCNClPQRTho\r\nutmWUxE6ukUriNDHfCA6SfB0X/yosm1IEkfEDChapZ0b28MIU+oSRWHMDhCg\r\nwLetbAYpeuba4WY9N6g+VOiBZuw7DNvMIF0aEp7nWOu7anOsjp7dEPP448Gz\r\nuB4D5zMekfIsiklPuVKw2x41+9iDhfCrL49HH0O2Odm0W1Fz/qMMTxog406R\r\n4FUXYMIFGIr3y8ltK81TiLUrsm1umUQJ2dkvXk531lsvv5ormZYnqzUV51sX\r\nUOE59p78Vo1RYKzaSvWo1nRonRP6iUdtsycUGc/424rsXHDpsh9rPOFBwtJW\r\navExxvkrLSbsReSV3MGleP0bRwVWC6oV/Af34IIG/OOS+h/cFpGB6NdgxSKN\r\nrWIHBI0LtKb1f081HF+SikuC4Vrf+1BHRUQlNPsaK2YSJEPkia9OYov2YTH0\r\nGo2EeyY7H/64Gc23UnsTCfgwBMIyrp3MWX73H+6VclnRkGgERbRXRQkIHFgA\r\nP8gBQCMm2TrQcGnA/6TXoEGDbdseGphF3Up0X1JGjizDnWwlnY/qCxNp+MZi\r\neFut0drKTV0dPVpcQBiY8ADfhnyc15nCSouSm7Qi5L0eH9LCJwv7E3XlO+0x\r\nm/WMU45/617BvIGl/1ZKWGSM3ulheiZR030=\r\n=rY7Z\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"franky47","email":"accounts@francoisbest.com"},"directories":{},"maintainers":[{"name":"franky47","email":"accounts@francoisbest.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/opaque-server_2.1.3_1677020096316_0.6523609457146957"},"_hasShrinkwrap":false},"2.1.4":{"name":"@47ng/opaque-server","collaborators":["Marcin Lazar <marcin.lazar@pm.me>","Hawkheart","r0kk3rz","François Best <npm.opaque@francoisbest.com>"],"description":"An implementation of the OPAQUE key exchange protocol in WASM(WebAssembly)","version":"2.1.4","repository":{"type":"git","url":"git+https://github.com/47ng/opaque.git","directory":"packages/server"},"main":"opaque-server.js","types":"opaque-server.d.ts","keywords":["cryptography","crypto","opaque","passwords","authentication","wasm","js","browser","webassembly","node","wasm-pack","bindgen","wasm-bindgen"],"type":"commonjs","license":"(Apache-2.0 OR MIT)","publishConfig":{"access":"public"},"devDependencies":{"sceau":"^1.3.0"},"scripts":{},"bugs":{"url":"https://github.com/47ng/opaque/issues"},"homepage":"https://github.com/47ng/opaque#readme","_id":"@47ng/opaque-server@2.1.4","_integrity":"sha512-ATzXS6rxOLUxnhsgbFPZFM5KgZvFKbxEUN1cT6vL2Oh8yl+3TBNyHncUBSWp7d623RFvAwoDW1ittq9XZ+o5qg==","_resolved":"/tmp/41107355291bdb191b9748ef174e431e/47ng-opaque-server-2.1.4.tgz","_from":"file:47ng-opaque-server-2.1.4.tgz","_nodeVersion":"18.14.0","_npmVersion":"9.3.1","dist":{"integrity":"sha512-ATzXS6rxOLUxnhsgbFPZFM5KgZvFKbxEUN1cT6vL2Oh8yl+3TBNyHncUBSWp7d623RFvAwoDW1ittq9XZ+o5qg==","shasum":"81fa31774ece051e28a3477c6b202105a60355aa","tarball":"https://registry.npmjs.org/@47ng/opaque-server/-/opaque-server-2.1.4.tgz","fileCount":6,"unpackedSize":194459,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIBFicO1FIJkoLuY79pvIJ6iIYIXi47kckLMO+VzjfjWOAiEAtdEW1k2QbcpeM3556HL6nWJEmxrJ/MRMVoKlkGr6fhY="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJj9U75ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmoGpQ/9GaysbkYiJm3vA9BWnMCmkw4QRR+p0OH9MXgPVnwCepuBPVYA\r\nRAQjCj9pAkHN9v+mAGsk4YvRWnl3BeraygxQrBJQ8JtC2BPEgizX3t3UiUce\r\nICxBK3dRHvMxKRUc/aLcFU37ZGCZBDUTDfHEQeUJQQGNfkAcPFD7kbo05N0z\r\nvKjybekHV3Zh8tWimjJ8D01xMMAQf9H1Sh7VivQULNNmUz/BFzGikMlxgpI4\r\n1iJ8FoeHurIp4jUHV6v8nzEDNJ4X5M9pC3GN5mcVjH6lsDnfYBJo5n3LIZej\r\nl/tJyNPDAvak/MxxU/TxMEJ1WUmCG4RE8qS+DK3RXI40TQimp93kCFsjq03S\r\njZW8DwmK9xMPFEEsFHm6nBjJwPZlG4n0je+Ubo5T38Di6Q7YH/nU6JjXMH4Z\r\nkAl89Wm3JEQyqmNv1Xmf1vHuWdQOVhP3J7etXWNVy+dM6rJWWEq1hcu1wS1D\r\nHhW0whimncQdCnBtUlhRv6HHi5Q54pCk8v+e1dCbdQJW4YBu+ngVIzvo92lG\r\nt7zNmVhzwS2fdZSWfvmf+bwA4RFrh8rxuspScbUe+kaDnIAt2kBtF9sLjjfq\r\njAtPZNx7HTCXHeUpUDu/WBrHOp4oUSMXra5r06qSHdaYlwqnk7/ojWmcn4w3\r\n762ByUEjLso6pjHb+2OTANZmE5ak818yljo=\r\n=XnPL\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"franky47","email":"accounts@francoisbest.com"},"directories":{},"maintainers":[{"name":"franky47","email":"accounts@francoisbest.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/opaque-server_2.1.4_1677020920938_0.30220151328432676"},"_hasShrinkwrap":false},"2.1.5":{"name":"@47ng/opaque-server","collaborators":["Marcin Lazar <marcin.lazar@pm.me>","Hawkheart","r0kk3rz","François Best <npm.opaque@francoisbest.com>"],"description":"An implementation of the OPAQUE key exchange protocol in WASM(WebAssembly)","version":"2.1.5","repository":{"type":"git","url":"git+https://github.com/47ng/opaque.git","directory":"packages/server"},"main":"opaque-server.js","types":"opaque-server.d.ts","keywords":["cryptography","crypto","opaque","passwords","authentication","wasm","js","browser","webassembly","node","wasm-pack","bindgen","wasm-bindgen"],"type":"commonjs","license":"(Apache-2.0 OR MIT)","publishConfig":{"access":"public"},"devDependencies":{"sceau":"^1.3.0"},"scripts":{},"bugs":{"url":"https://github.com/47ng/opaque/issues"},"homepage":"https://github.com/47ng/opaque#readme","_id":"@47ng/opaque-server@2.1.5","_integrity":"sha512-5zWFt4bedJTaYwNT+F3kinZrAy1dpcK7wWg2co6fVHGnjR5Y9bAjKkramNwUwbMjKkaCLVJGbX5oqxZWiieWuQ==","_resolved":"/tmp/82b509ae8952eb4aee847067405f2b72/47ng-opaque-server-2.1.5.tgz","_from":"file:47ng-opaque-server-2.1.5.tgz","_nodeVersion":"18.15.0","_npmVersion":"9.5.0","dist":{"integrity":"sha512-5zWFt4bedJTaYwNT+F3kinZrAy1dpcK7wWg2co6fVHGnjR5Y9bAjKkramNwUwbMjKkaCLVJGbX5oqxZWiieWuQ==","shasum":"d1d0f28e287f330de01912f7718995bbc56c30ef","tarball":"https://registry.npmjs.org/@47ng/opaque-server/-/opaque-server-2.1.5.tgz","fileCount":6,"unpackedSize":262721,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCK203emVTaIK+AC+czsIA9vEi23hHrOC7dnYy5gO+M9AIhANPPkrJ94S27InVEKONVRdqUqlcyhm12Dkno7ryzcBwj"}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJkHdQnACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmoRUg/9F0d1A6AC74MyyGu2i+rSTiQkFnLqGNIqMKR9T2+WccYc1Mlp\r\nJGFSQLQBvPebCF+j/+grO52QrMnttaDCHovUkRWymqnacAMZU/zmIWRq8/qU\r\nh3ezrtM6vbJKJ0I31AqcErIoPsNC7GJZD+AOJ9Wz0NhePl97NDTg2oF/z5di\r\nz34gIWfFlJ1C1Ul3aVjM/uchGG4PH7lzGIYn8Rwt/bQFSz9tUPmzhkrSpIoF\r\npM9Hi3yUwya/nFFS3yhflRYhdPwrNY1u7dJRq+rTTpIQkPLDowr1mMfHDnBQ\r\naHzKCLGJsTkicsyNJ3wvqQznVf4Fr5gfGf4bT6SbM4R2C4W9Kfya972Rt2WB\r\ng4i87TB1uZfk4LeQMSxa26t9Iy24neHvO9jbiHotA53EfFCjga7A1qHDzdRq\r\nbtH9OoP8kO5C6T5hcSyt7vzPGO4AxC4Qs7rfvWVqzL30AbwiMfovLFEYPMXU\r\n5sxBT7bW/JqspBoxttKPcOI0ASG6ggD79sggqPBdeCEcM92qOjTKWCZkjejB\r\nM4dTUd3eawnJE+948AGuF/HwlgAJ+L3wMwAHAGOSptMU/gGBQAauRl7Cs9er\r\nVxGmSnDhQAsgO3FQ9qqbFPz3Ku0aBtsvA7P7ThkWUb4or6mpPF/DfjZqjHwe\r\nhQ3KiZBpKpTVzSkGb4AIr2AZrHQD1oUeiTw=\r\n=IaDE\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"franky47","email":"accounts@francoisbest.com"},"directories":{},"maintainers":[{"name":"franky47","email":"accounts@francoisbest.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/opaque-server_2.1.5_1679676455173_0.5544641925033196"},"_hasShrinkwrap":false}},"time":{"created":"2023-01-31T22:59:00.322Z","0.0.1-beta.7":"2023-01-31T22:59:00.669Z","modified":"2023-03-24T16:47:35.414Z","0.0.1-beta.8":"2023-01-31T23:19:58.434Z","0.0.1-beta.9":"2023-01-31T23:29:57.338Z","0.0.1-beta.10":"2023-01-31T23:39:17.372Z","0.0.1-beta.11":"2023-02-01T11:10:14.743Z","0.0.1-beta.13":"2023-02-01T12:54:56.555Z","0.0.1-beta.14":"2023-02-01T13:18:23.623Z","0.0.1-beta.15":"2023-02-02T13:18:30.826Z","0.0.1-beta.16":"2023-02-03T11:40:01.031Z","1.0.0":"2023-02-03T12:32:04.677Z","2.1.0":"2023-02-03T12:43:01.634Z","2.1.1":"2023-02-16T13:47:58.428Z","2.1.2":"2023-02-21T22:47:53.992Z","2.1.3":"2023-02-21T22:54:56.488Z","2.1.4":"2023-02-21T23:08:41.125Z","2.1.5":"2023-03-24T16:47:35.270Z"},"maintainers":[{"name":"franky47","email":"accounts@francoisbest.com"}],"description":"An implementation of the OPAQUE key exchange protocol in WASM(WebAssembly)","homepage":"https://github.com/47ng/opaque#readme","keywords":["cryptography","crypto","opaque","passwords","authentication","wasm","js","browser","webassembly","node","wasm-pack","bindgen","wasm-bindgen"],"repository":{"type":"git","url":"git+https://github.com/47ng/opaque.git","directory":"packages/server"},"bugs":{"url":"https://github.com/47ng/opaque/issues"},"license":"(Apache-2.0 OR MIT)","readme":"# `@47ng/opaque-server`\n\nThe OPAQUE key exchange protocol in WASM (WebAssembly), for Node.js.\nThis implementation is based on [facebook/opaque-ke](https://github.com/facebook/opaque-ke).\n\nBuilt as CJS for Node.js from [47ng/opaque-wasm](https://github.com/47ng/opaque-wasm/tree/fork/47ng-opaque/do-not-merge-to-upstream)\n_(a fork of [marucjmar/opaque-wasm](https://github.com/marucjmar/opaque-wasm) using Ristretto rather than the NIST P-256 curve)_.\n\nClient (browser) counterpart is available in [`@47ng/opaque-client`](https://npmjs.com/package/@47ng/opaque-client).\n\n## Installation\n\n```\nnpm install @47ng/opaque-server\nyarn add @47ng/opaque-server\npnpm add @47ng/opaque-server\n```\n\n## Usage\n\nThis implements the [OPAQUE protocol overview](https://github.com/47ng/opaque/blob/main/docs/opaque-protocol-overview.md)\nfor a stateless server and with recommended security practices.\n\n### Setup\n\nYou'll need to create a `ServerSetup` first, which is to be treated as a secret.\n\nChanging it will invalidate your existing saved credentials, as it allows\nclients to also authenticate your server (mutual authentication).\nYou can treat it kind of like a signature private key.\n\n```ts\nimport { ServerSetup } from '@47ng/opaque-server'\nimport { hex } from '@47ng/codec'\n\nconst serverSetup = ServerSetup.serialize(new ServerSetup())\n\n// serverSetup is a byte array, you can convert it to whatever\n// string format suits your application (eg: hexadecimal here):\nconsole.info(`Generated OPAQUE server setup: ${hex.encode(serverSetup)}`)\n```\n\nWhen starting your server, assuming you obtain this serialized `ServerSetup`\nfrom a secure location (a secret management service like Hashicorp Vault,\na mounted file or an environment variable), you can hydrate it like so:\n\n```ts\nimport { ServerSetup } from '@47ng/opaque-server'\nimport { hex } from '@47ng/codec'\n\nconst serverSetup = ServerSetup.deserialize(\n  hex.decode(OPAQUE_SERIALIZED_SERVER_SETUP)\n)\n```\n\nYou will then pass this server setup to the Registration and Login handlers.\n\n### Registration (signup)\n\nOPAQUE requires two handshakes to perform a signup (technically one and a half,\nthe final response has no cryptographic use to the client):\n\nPseudo-code:\n\n```ts\nimport { HandleRegistration } from '@47ng/opaque-server'\nimport crypto from 'node:crypto'\n\n// Request handler 1 (example path: `/registration/request`)\nasync function opaqueRegistrationRequest(request, response) {\n  const registration = new HandleRegistration(serverSetup)\n  const registrationResponse = registration.start(\n    request.body.username,\n    request.body.registrationRequest\n  )\n  const nonce = crypto.randomBytes(32).toString('hex')\n  await keyValueStore.set({\n    key: nonce,\n    value: request.body.username,\n    ttl: 120, // Something short, here 2 minutes\n  })\n  response.send(registrationResponse)\n  registration.free()\n}\n\n// Request handler 2 (example path: `/registration/record`)\nasync function opaqueRegistrationRecord(request, response) {\n  const registration = new HandleRegistration(serverSetup)\n  // Do not trust client-provided usernames in the request body here:\n  // https://github.com/facebook/opaque-ke/issues/276#issuecomment-1162609521\n  const username = await keyValueStore.get({\n    key: request.body.nonce,\n  })\n  const passwordFile = registration.finish(request.body.registrationRecord)\n  await db.insert({\n    username,\n    passwordFile,\n  })\n  await keyValueStore.del({ key: request.body.nonce })\n  response.status(204).send()\n  // Note: there is no need to free the `registration` object here,\n  // it's been taken care of by the call to `finish`.\n}\n```\n\n> _Note: registration doesn't perform key exchange/agreement,\n> so a login step is necessary after signup to establish a shared key._\n\n### Login\n\nOPAQUE requires two handshakes to perform a login.\n\nAt the end of the second handshake, the server will be able to use the key\nagreed upon, and the client will already have the same key, so you can start\nusing that key from the second response.\n\nPseudo-code:\n\n```ts\nimport { HandleLogin } from '@47ng/opaque-server'\n\n// Request handler 1 (example path: `/login/request`)\nasync function opaqueLoginRequest(request, response) {\n  const login = new HandleLogin(serverSetup)\n  const { passwordFile } = await db.findOne({\n    where: { username: request.body.username },\n  })\n  const loginResponse = login.start(\n    passwordFile,\n    request.body.username,\n    request.body.loginRequest\n  )\n  const loginState = login.serialize()\n  const nonce = crypto.randomBytes(32).toString('hex')\n  await keyValueStore.set({\n    key: nonce,\n    value: {\n      username: request.body.username,\n      loginState,\n    },\n    ttl: 120, // Something short, here 2 minutes\n  })\n  response.send({\n    nonce,\n    loginResponse,\n  })\n  login.free()\n}\n\n// Request handler 2 (example path: `/login/final`)\nasync function opaqueLoginFinal(request, response) {\n  const { username, loginState } = await keyValueStore.get({\n    key: request.body.nonce,\n  })\n  const login = HandleLogin.deserialize(loginState)\n  const sessionKey = login.finish(request.body.loginFinal)\n  await keyValueStore.del({ key: request.body.nonce })\n  response.setAuthCookiesFor(username)\n  response.send(...)\n  // Note: there is no need to free the `login` object here,\n  // it's been taken care of by the call to `finish`.\n}\n```\n","readmeFilename":"README.md"}