Parcourir la source

[web] Promisify two-factor-authentication (#19004)

* Fixup typos

* Promisify `getPendingUser`

* Promisify `getPendingUserWithExpectations`

* Add promises to `AccessTokenEncryptor`

* Promisify `TwoFactorAuthenticationHandler`

* Promisify/Expressify `TwoFactorAuthenticationController`

* Centralise `unprocessableEntity` error handling into a function

* Fixup: entralise `unprocessableEntity` error handling into a function

Avoid "responding to the query twice"

* Remove unnecessary try/catch

* Fixup: Add `async` to AccessTokenEncryptor promises

* Add tests on `AccessTokenEncryptor.promises`

* Revert "Fixup: entralise `unprocessableEntity` error handling into a function"

This reverts commit 23bd9d73260733062908b98961846733c61171e9.

* Revert "Centralise `unprocessableEntity` error handling into a function"

This reverts commit 197ca3da02412d5224d411b29df1d7b9e5327d01.

GitOrigin-RevId: 1a2864d28e87fd5e48cd3723a3da8047b79a1596
Antoine Clausse il y a 2 ans
Parent
commit
fc34b41b5b

+ 6 - 0
libraries/access-token-encryptor/.mocharc.json

@@ -0,0 +1,6 @@
+{
+  "ui": "bdd",
+  "recursive": "true",
+  "reporter": "spec",
+  "require": "test/setup.js"
+}

+ 20 - 9
libraries/access-token-encryptor/lib/js/AccessTokenEncryptor.js

@@ -92,6 +92,9 @@ class AccessTokenSchemeV3 extends AccessTokenSchemeWithGenericKeyFn {
 
 class AccessTokenEncryptor {
   constructor(settings) {
+    /**
+     * @type {Map<string, AbstractAccessTokenScheme>}
+     */
     this.schemeByCipherLabel = new Map()
     for (const cipherLabel of Object.keys(settings.cipherPasswords)) {
       if (!cipherLabel) {
@@ -128,25 +131,33 @@ class AccessTokenEncryptor {
       this.schemeByCipherLabel.set(cipherLabel, scheme)
     }
 
+    /** @type {AbstractAccessTokenScheme} */
     this.defaultScheme = this.schemeByCipherLabel.get(settings.cipherLabel)
     if (!this.defaultScheme) {
       throw new Error(`unknown default cipherLabel ${settings.cipherLabel}`)
     }
   }
 
+  promises = {
+    encryptJson: async json => await this.defaultScheme.encryptJson(json),
+    decryptToJson: async encryptedJson => {
+      const [label] = encryptedJson.split(':', 1)
+      const scheme = this.schemeByCipherLabel.get(label)
+      if (!scheme) {
+        throw new Error('unknown access-token-encryptor label ' + label)
+      }
+      return await scheme.decryptToJson(encryptedJson)
+    },
+  }
+
   encryptJson(json, callback) {
-    this.defaultScheme.encryptJson(json).then(s => callback(null, s), callback)
+    this.promises.encryptJson(json).then(s => callback(null, s), callback)
   }
 
   decryptToJson(encryptedJson, callback) {
-    const [label] = encryptedJson.split(':', 1)
-    const scheme = this.schemeByCipherLabel.get(label)
-    if (!scheme) {
-      return callback(
-        new Error('unknown access-token-encryptor label ' + label)
-      )
-    }
-    scheme.decryptToJson(encryptedJson).then(o => callback(null, o), callback)
+    this.promises
+      .decryptToJson(encryptedJson)
+      .then(o => callback(null, o), callback)
   }
 }
 

+ 1 - 0
libraries/access-token-encryptor/package.json

@@ -20,6 +20,7 @@
   },
   "devDependencies": {
     "chai": "^4.3.6",
+    "chai-as-promised": "^7.1.1",
     "mocha": "^10.2.0",
     "sandboxed-module": "^2.0.4",
     "typescript": "^5.0.4"

+ 4 - 0
libraries/access-token-encryptor/test/setup.js

@@ -0,0 +1,4 @@
+const chai = require('chai')
+const chaiAsPromised = require('chai-as-promised')
+
+chai.use(chaiAsPromised)

+ 145 - 65
libraries/access-token-encryptor/test/unit/js/AccessTokenEncryptorTests.js

@@ -122,103 +122,183 @@ describe('AccessTokenEncryptor', function () {
     })
   })
 
-  describe('encrypt', function () {
-    it('should encrypt the object', function (done) {
-      this.encryptor.encryptJson(this.testObject, (err, encrypted) => {
-        expect(err).to.be.null
-        encrypted.should.match(
-          /^2023.1-v3:[0-9a-f]{32}:[a-zA-Z0-9=+/]+:[0-9a-f]{32}$/
-        )
-        done()
+  describe('sync', function () {
+    describe('encrypt', function () {
+      it('should encrypt the object', function (done) {
+        this.encryptor.encryptJson(this.testObject, (err, encrypted) => {
+          expect(err).to.be.null
+          encrypted.should.match(
+            /^2023.1-v3:[0-9a-f]{32}:[a-zA-Z0-9=+/]+:[0-9a-f]{32}$/
+          )
+          done()
+        })
+      })
+
+      it('should encrypt the object differently the next time', function (done) {
+        this.encryptor.encryptJson(this.testObject, (err, encrypted1) => {
+          expect(err).to.be.null
+          this.encryptor.encryptJson(this.testObject, (err, encrypted2) => {
+            expect(err).to.be.null
+            encrypted1.should.not.equal(encrypted2)
+            done()
+          })
+        })
       })
     })
 
-    it('should encrypt the object differently the next time', function (done) {
-      this.encryptor.encryptJson(this.testObject, (err, encrypted1) => {
-        expect(err).to.be.null
-        this.encryptor.encryptJson(this.testObject, (err, encrypted2) => {
+    describe('decrypt', function () {
+      it('should decrypt the string to get the same object', function (done) {
+        this.encryptor.encryptJson(this.testObject, (err, encrypted) => {
           expect(err).to.be.null
-          encrypted1.should.not.equal(encrypted2)
+          this.encryptor.decryptToJson(encrypted, (err, decrypted) => {
+            expect(err).to.be.null
+            expect(decrypted).to.deep.equal(this.testObject)
+            done()
+          })
+        })
+      })
+
+      it('should not be able to decrypt 2015 string', function (done) {
+        this.encryptor.decryptToJson(this.encrypted2015, (err, decrypted) => {
+          expect(err).to.exist
+          expect(err.message).to.equal(
+            'unknown access-token-encryptor label 2015.1'
+          )
+          expect(decrypted).to.not.exist
+          done()
+        })
+      })
+
+      it('should not be able to decrypt a 2016 string', function (done) {
+        this.encryptor.decryptToJson(this.encrypted2016, (err, decrypted) => {
+          expect(err).to.exist
+          expect(err.message).to.equal(
+            'unknown access-token-encryptor label 2016.1'
+          )
+          expect(decrypted).to.not.exist
+          done()
+        })
+      })
+
+      it('should not be able to decrypt a 2019 string', function (done) {
+        this.encryptor.decryptToJson(this.encrypted2019, (err, decrypted) => {
+          expect(err).to.exist
+          expect(err.message).to.equal(
+            'unknown access-token-encryptor label 2019.1'
+          )
+          expect(decrypted).to.not.exist
           done()
         })
       })
-    })
-  })
 
-  describe('decrypt', function () {
-    it('should decrypt the string to get the same object', function (done) {
-      this.encryptor.encryptJson(this.testObject, (err, encrypted) => {
-        expect(err).to.be.null
-        this.encryptor.decryptToJson(encrypted, (err, decrypted) => {
+      it('should decrypt an 2023 string to get the same object', function (done) {
+        this.encryptor.decryptToJson(this.encrypted2023, (err, decrypted) => {
           expect(err).to.be.null
           expect(decrypted).to.deep.equal(this.testObject)
           done()
         })
       })
+
+      it('should return an error when decrypting an invalid label', function (done) {
+        this.encryptor.decryptToJson(this.badLabel, (err, decrypted) => {
+          expect(err).to.be.instanceof(Error)
+          expect(decrypted).to.be.undefined
+          done()
+        })
+      })
+
+      it('should return an error when decrypting an invalid key', function (done) {
+        this.encryptor.decryptToJson(this.badKey, (err, decrypted) => {
+          expect(err).to.be.instanceof(Error)
+          expect(decrypted).to.be.undefined
+          done()
+        })
+      })
+
+      it('should return an error when decrypting an invalid ciphertext', function (done) {
+        this.encryptor.decryptToJson(this.badCipherText, (err, decrypted) => {
+          expect(err).to.be.instanceof(Error)
+          expect(decrypted).to.be.undefined
+          done()
+        })
+      })
     })
+  })
 
-    it('should not be able to decrypt 2015 string', function (done) {
-      this.encryptor.decryptToJson(this.encrypted2015, (err, decrypted) => {
-        expect(err).to.exist
-        expect(err.message).to.equal(
-          'unknown access-token-encryptor label 2015.1'
+  describe('async', function () {
+    describe('encrypt', function () {
+      it('should encrypt the object', async function () {
+        const encrypted = await this.encryptor.promises.encryptJson(
+          this.testObject
+        )
+        encrypted.should.match(
+          /^2023.1-v3:[0-9a-f]{32}:[a-zA-Z0-9=+/]+:[0-9a-f]{32}$/
         )
-        expect(decrypted).to.not.exist
-        done()
       })
-    })
 
-    it('should not be able to decrypt a 2016 string', function (done) {
-      this.encryptor.decryptToJson(this.encrypted2016, (err, decrypted) => {
-        expect(err).to.exist
-        expect(err.message).to.equal(
-          'unknown access-token-encryptor label 2016.1'
+      it('should encrypt the object differently the next time', async function () {
+        const encrypted1 = await this.encryptor.promises.encryptJson(
+          this.testObject
+        )
+        const encrypted2 = await this.encryptor.promises.encryptJson(
+          this.testObject
         )
-        expect(decrypted).to.not.exist
-        done()
+        encrypted1.should.not.equal(encrypted2)
       })
     })
 
-    it('should not be able to decrypt a 2019 string', function (done) {
-      this.encryptor.decryptToJson(this.encrypted2019, (err, decrypted) => {
-        expect(err).to.exist
-        expect(err.message).to.equal(
-          'unknown access-token-encryptor label 2019.1'
+    describe('decrypt', function () {
+      it('should decrypt the string to get the same object', async function () {
+        const encrypted = await this.encryptor.promises.encryptJson(
+          this.testObject
         )
-        expect(decrypted).to.not.exist
-        done()
+        const decrypted = await this.encryptor.promises.decryptToJson(encrypted)
+        expect(decrypted).to.deep.equal(this.testObject)
+      })
+
+      it('should not be able to decrypt 2015 string', async function () {
+        await expect(
+          this.encryptor.promises.decryptToJson(this.encrypted2015)
+        ).to.eventually.be.rejectedWith(
+          'unknown access-token-encryptor label 2015.1'
+        )
+      })
+
+      it('should not be able to decrypt a 2016 string', async function () {
+        await expect(
+          this.encryptor.promises.decryptToJson(this.encrypted2016)
+        ).to.be.rejectedWith('unknown access-token-encryptor label 2016.1')
+      })
+
+      it('should not be able to decrypt a 2019 string', async function () {
+        await expect(
+          this.encryptor.promises.decryptToJson(this.encrypted2019)
+        ).to.be.rejectedWith('unknown access-token-encryptor label 2019.1')
       })
-    })
 
-    it('should decrypt an 2023 string to get the same object', function (done) {
-      this.encryptor.decryptToJson(this.encrypted2023, (err, decrypted) => {
-        expect(err).to.be.null
+      it('should decrypt an 2023 string to get the same object', async function () {
+        const decrypted = await this.encryptor.promises.decryptToJson(
+          this.encrypted2023
+        )
         expect(decrypted).to.deep.equal(this.testObject)
-        done()
       })
-    })
 
-    it('should return an error when decrypting an invalid label', function (done) {
-      this.encryptor.decryptToJson(this.badLabel, (err, decrypted) => {
-        expect(err).to.be.instanceof(Error)
-        expect(decrypted).to.be.undefined
-        done()
+      it('should return an error when decrypting an invalid label', async function () {
+        await expect(
+          this.encryptor.promises.decryptToJson(this.badLabel)
+        ).to.be.rejectedWith('unknown access-token-encryptor label xxxxxx')
       })
-    })
 
-    it('should return an error when decrypting an invalid key', function (done) {
-      this.encryptor.decryptToJson(this.badKey, (err, decrypted) => {
-        expect(err).to.be.instanceof(Error)
-        expect(decrypted).to.be.undefined
-        done()
+      it('should return an error when decrypting an invalid key', async function () {
+        await expect(
+          this.encryptor.promises.decryptToJson(this.badKey)
+        ).to.be.rejectedWith('unknown access-token-encryptor label 2015.1')
       })
-    })
 
-    it('should return an error when decrypting an invalid ciphertext', function (done) {
-      this.encryptor.decryptToJson(this.badCipherText, (err, decrypted) => {
-        expect(err).to.be.instanceof(Error)
-        expect(decrypted).to.be.undefined
-        done()
+      it('should return an error when decrypting an invalid ciphertext', async function () {
+        await expect(
+          this.encryptor.promises.decryptToJson(this.badCipherText)
+        ).to.be.rejectedWith('unknown access-token-encryptor label 2015.1')
       })
     })
   })

+ 2 - 0
package-lock.json

@@ -88,6 +88,7 @@
       },
       "devDependencies": {
         "chai": "^4.3.6",
+        "chai-as-promised": "^7.1.1",
         "mocha": "^10.2.0",
         "sandboxed-module": "^2.0.4",
         "typescript": "^5.0.4"
@@ -51743,6 +51744,7 @@
       "version": "file:libraries/access-token-encryptor",
       "requires": {
         "chai": "^4.3.6",
+        "chai-as-promised": "^7.1.1",
         "lodash": "^4.17.21",
         "mocha": "^10.2.0",
         "sandboxed-module": "^2.0.4",