Skip to content

Quick-start

To get started quickly, we'll integrate the encryption & decryption features directly into the sample project, by password protecting the identity.

The branch on which this step is based is master, the end result is 1-quick-start.

Installation

On the front-end, we will use the web version of the SDK @seald-io/sdk with the password identity protection module @seald-io/sdk-plugin-ssks-password:

shell
cd frontend
npm install --save @seald-io/sdk @seald-io/sdk-plugin-ssks-password
cd frontend
npm install --save @seald-io/sdk @seald-io/sdk-plugin-ssks-password

On the back-end, we will need to generate JSON Web Tokens. To do this, we install the jose module:

shell
cd ../backend
npm install --save jose
cd ../backend
npm install --save jose

Configuration

To configure the SDK, you have to connect to your administration dashboard and get the following elements:

  • appId: unique UUID for your application;
  • JWT_SHARED_SECRET_ID : unique UUID of the shared secret;
  • JWT_SHARED_SECRET : shared secret corresponding to the JWTSecret used to generate JWT.
  • apiURL: URL of the API server to use;
  • keyStorageURL: URL of the identity storage server to use.

TIP

JWT secrets can be generated in the settings, tab JWT secrets.

For more information refer to this documentation.

TIP

To create a developer account, follow the first steps.

To instantiate the SDK, we proceed as follows:

js
import SealdSDK from '@seald-io/sdk'
import SealdSDKPluginSSKSPassword from '@seald-io/sdk-plugin-ssks-password'
/* ... */

let sealdSDKInstance = null

const instantiateSealdSDK = async () => {
  sealdSDKInstance = SealdSDK({
    appId: await getSetting('APP_ID'),
    apiURL: await getSetting('API_URL'),
    plugins: [SealdSDKPluginSSKSPassword(await getSetting('KEY_STORAGE_URL'))]
  })
}

export const getSealdSDKInstance = () => sealdSDKInstance
import SealdSDK from '@seald-io/sdk'
import SealdSDKPluginSSKSPassword from '@seald-io/sdk-plugin-ssks-password'
/* ... */

let sealdSDKInstance = null

const instantiateSealdSDK = async () => {
  sealdSDKInstance = SealdSDK({
    appId: await getSetting('APP_ID'),
    apiURL: await getSetting('API_URL'),
    plugins: [SealdSDKPluginSSKSPassword(await getSetting('KEY_STORAGE_URL'))]
  })
}

export const getSealdSDKInstance = () => sealdSDKInstance

TIP

Since the SDK instance is shared across an entire application in React, it is better to store it in a variable in a seald.js service file and expose a getter rather than storing it in an immutable variable.

Seald identity Management

When creating a Seald identity, it must be associated to the Seald developer account, and to the user (whose identifier is noted userId) in the application in which the SDK is integrated.

This requires generating a JSON Web Token called signupJWT from the JWT_SECRET, and the JWT_SECRET_ID.

Generating a signupJWT

The generation of a signupJWT is done by your backend, and is described in this dedicated guide.

You'll find examples of how to create signupJWT in the most common backend languages in this section

Your backend must generate a signupJWT during the account creation API call, then return the token in the call response.

WARNING

In production, you must not give the frontend knowledge of JWT_SHARED_SECRET. If it were, it could be used by an attacker to create new accounts associated with your developer account, spend your user licenses, and possibly reassign some of your user's userIDs to Seald accounts that they control.

You can refer to the guide dedicated to JWT generation.

js
// utils for `generateSignupJWT`
const randomString = (length = 10) => randomBytes(length)
  .then(randomBytes => randomBytes
    .toString('base64')
    .replace(/[^a-z0-9]/gi, '')
    .slice(0, length)
  )

const generateSignupJWT = async (userId) => {
  const token = new jose.SignJWT({
    iss: settings.JWT_SHARED_SECRET_ID,
    jti: await randomString(), // So the JWT is only usable once.
    iat: Math.floor(Date.now() / 1000), // JWT valid only for 10 minutes. `Date.now()` returns the timestamp in milliseconds, this needs it in seconds.
    join_team: true,
    connector_add: {
      value: `${userId}@${settings.APP_ID}`,
      type: 'AP'
    }
  })
    .setProtectedHeader({ alg: 'HS256' })
  return token.sign(Buffer.from(settings.JWT_SHARED_SECRET, 'ascii'))
}

router.post('/', validate(createAccountValidator), async (req, res, next) => {
  /* ... Acount creation code ... */
  res.json({
    user: user.serialize(),
    signupJWT: await generateSignupJWT(JWT_SHARED_SECRET, JWT_SHARED_SECRET_ID, APP_ID, userId)
  })
})
// utils for `generateSignupJWT`
const randomString = (length = 10) => randomBytes(length)
  .then(randomBytes => randomBytes
    .toString('base64')
    .replace(/[^a-z0-9]/gi, '')
    .slice(0, length)
  )

const generateSignupJWT = async (userId) => {
  const token = new jose.SignJWT({
    iss: settings.JWT_SHARED_SECRET_ID,
    jti: await randomString(), // So the JWT is only usable once.
    iat: Math.floor(Date.now() / 1000), // JWT valid only for 10 minutes. `Date.now()` returns the timestamp in milliseconds, this needs it in seconds.
    join_team: true,
    connector_add: {
      value: `${userId}@${settings.APP_ID}`,
      type: 'AP'
    }
  })
    .setProtectedHeader({ alg: 'HS256' })
  return token.sign(Buffer.from(settings.JWT_SHARED_SECRET, 'ascii'))
}

router.post('/', validate(createAccountValidator), async (req, res, next) => {
  /* ... Acount creation code ... */
  res.json({
    user: user.serialize(),
    signupJWT: await generateSignupJWT(JWT_SHARED_SECRET, JWT_SHARED_SECRET_ID, APP_ID, userId)
  })
})
js
// Retreive the user's infos and his associated signup JWT
const { user: { id }, signupJWT } = await apiClient.rest.account.create({
  emailAddress,
  password,
  name
})
// Retreive the user's infos and his associated signup JWT
const { user: { id }, signupJWT } = await apiClient.rest.account.create({
  emailAddress,
  password,
  name
})

Creating an identity

Once the token is generated, a new identity can be created as follows:

js
const accountInfo = await sealdSDKInstance.initiateIdentity({ signupJWT })
const accountInfo = await sealdSDKInstance.initiateIdentity({ signupJWT })

The function returns an object of type accountInfo. This object contains information about the newly created account, including an identifier called sealdId. You'll need to save this identifier and associate it with the user on your back-end, and in the front-end.

The following functions can be used to save this identifier in the front-end and back-end model.

js
router.post('/sealdId', validate(sealdIdValidator), async (req, res, next) => {
  try {
    const { sealdId } = req.body
    const userId = req.session.user.id
    const user = await User.findOne({ where: { id: userId } })
    await user.setSealdId(sealdId)
    global.io.emit('user:created', user.serialize())
    res.json({ sealdId })
  } catch (error) {
    next(error)
  }
})
router.post('/sealdId', validate(sealdIdValidator), async (req, res, next) => {
  try {
    const { sealdId } = req.body
    const userId = req.session.user.id
    const user = await User.findOne({ where: { id: userId } })
    await user.setSealdId(sealdId)
    global.io.emit('user:created', user.serialize())
    res.json({ sealdId })
  } catch (error) {
    next(error)
  }
})
js
await currentUser.setSealdId(sealdId)
await currentUser.setSealdId(sealdId)

Once this identifier has been saved, the SDK instance is ready to encrypt and decrypt, and the identity-specific keys are in memory.

Password-protection of the identity

Once the identity is created, it exists only in memory, it must be saved to be used in a later session.

To do this we will use the @seald-io/sdk-plugin-ssks-password module which allows to protect the identity keys with a password.

js
await sealdSDKInstance.ssksPassword.saveIdentity({
  userId,
  password
})
await sealdSDKInstance.ssksPassword.saveIdentity({
  userId,
  password
})

Once this function is executed, the identity is saved and can be retrieved using the equivalent function sealdSDKInstance.ssksPassword.retrieveIdentity from a newly instantiated instance.

For more details on this mechanism, refer to the guide dedicated to password protection.

TIP

For faster execution, as an advanced usage of the SDK, you can customize the password derivation into a rawEncryptionKey and a rawStorageKey, and pass these instead of the password to ssksPassword.saveIdentity. For more details, see the "Customize the password derivation" section of the Identity Guide.

Exposing a createIdentity and retrieveIdentity functions

If we gather all the elements studied in the previous paragraphs, we can expose two functions:

js
export const createIdentity = async ({ userId, password, signupJWT }) => {
  await instantiateSealdSDK()
  const accountInfo = await sealdSDKInstance.initiateIdentity({ signupJWT })
  await sealdSDKInstance.ssksPassword.saveIdentity({ userId, password })
  return accountInfo.sealdId
}

export const retrieveIdentity = async ({ userId, password }) => {
  await instantiateSealdSDK()
  return await sealdSDKInstance.ssksPassword.retrieveIdentity({ userId, password })
}
export const createIdentity = async ({ userId, password, signupJWT }) => {
  await instantiateSealdSDK()
  const accountInfo = await sealdSDKInstance.initiateIdentity({ signupJWT })
  await sealdSDKInstance.ssksPassword.saveIdentity({ userId, password })
  return accountInfo.sealdId
}

export const retrieveIdentity = async ({ userId, password }) => {
  await instantiateSealdSDK()
  return await sealdSDKInstance.ssksPassword.retrieveIdentity({ userId, password })
}

Integrate with account creation and login

The next step is to call these functions when creating the account in the application and when connecting.

To create it during account creation:

js
/* frontend/src/containers/SignUp.jsx */
import { createIdentity } from '../services/seald'

/* ... */
const currentUser = await User.createAccount({ emailAddress, password, name })
// right after the account is created on the back-end, let's create the Seald identity
await createIdentity({ userId: currentUser.id, password }) // TODO: don't use the clearText password both for account creation and identity protection
// Once the seald identity has been created, let's associate the `sealdId` with the user account
await currentUser.setSealdId(sealdId)
/* ... */
/* frontend/src/containers/SignUp.jsx */
import { createIdentity } from '../services/seald'

/* ... */
const currentUser = await User.createAccount({ emailAddress, password, name })
// right after the account is created on the back-end, let's create the Seald identity
await createIdentity({ userId: currentUser.id, password }) // TODO: don't use the clearText password both for account creation and identity protection
// Once the seald identity has been created, let's associate the `sealdId` with the user account
await currentUser.setSealdId(sealdId)
/* ... */

To retrieve upon login:

js
/* frontend/src/containers/SignIn.jsx */
import { retrieveIdentity } from '../services/seald'

/* ... */
const currentUser = await User.login({ emailAddress, password })
// right after the account is logged in on the back-end, let's retrieve the Seald identity
const accountInfo = await retrieveIdentity({ userId: currentUser.id, password }) // TODO: don't use the clearText password both for account creation and identity protection
/* ... */
/* frontend/src/containers/SignIn.jsx */
import { retrieveIdentity } from '../services/seald'

/* ... */
const currentUser = await User.login({ emailAddress, password })
// right after the account is logged in on the back-end, let's retrieve the Seald identity
const accountInfo = await retrieveIdentity({ userId: currentUser.id, password }) // TODO: don't use the clearText password both for account creation and identity protection
/* ... */

Since the identity is only stored in memory in the browser, if you open a new tab, you will have to retype the password. To get around this problem, we do not try to get the logged-in user's profile from the server:

js
/* frontend/src/App.js */
/* ... */
const currentUser = User.getCurrentUser() // || await User.updateCurrentUser() // TODO : Seald-SDK loads in memory, cannot retrieve it from session, must type password
/* ... */
/* frontend/src/App.js */
/* ... */
const currentUser = User.getCurrentUser() // || await User.updateCurrentUser() // TODO : Seald-SDK loads in memory, cannot retrieve it from session, must type password
/* ... */

We will see later how to make this identity persist in the browser.

DANGER

Here we put aside a major security concern: the authentication password should not be used as is to protect the identity. The authentication method must be modified (with a pre-derivation of the password) so that the application server cannot know the password. A dedicated guide is available here.

Encrypting & decrypting messages

To perform message encryption and decryption, you need to:

  • create a shared encryption session between the recipients;
  • encrypt messages before sending them;
  • decrypt messages after reception.

Encryption Session

To create a shared encryption session between users userId_1 and userId_2 in the roomId_1 chatroom, we proceed as follows:

js
/* frontend/src/components/Chat.jsx */
import { getSealdSDKInstance } from '../services/seald'

const sealdIds = ['sealdId_1', 'sealdId_2']
const metadata = 'roomId_1'
const session = await getSealdSDKInstance().createEncryptionSession({ sealdIds }, { metadata })
/* frontend/src/components/Chat.jsx */
import { getSealdSDKInstance } from '../services/seald'

const sealdIds = ['sealdId_1', 'sealdId_2']
const metadata = 'roomId_1'
const session = await getSealdSDKInstance().createEncryptionSession({ sealdIds }, { metadata })

This returns an EncryptionSession with a new unique sessionId assigned by the server.

In this project, we put the chat room identifier roomId in metadata.

To encrypt a message with a session, we proceed as follows:

js
/* frontend/src/components/Chat.jsx */
const clearText = 'hello world !'
const encryptedMessage = await session.encryptMessage(clearText)
/* frontend/src/components/Chat.jsx */
const clearText = 'hello world !'
const encryptedMessage = await session.encryptMessage(clearText)

This will give a string of the form:

js
'{"sessionId":"0000000000000000000000","data":"8RwaOppCD3uIJVFv2LoP3XGXpomj0xsMv4qmMVy30Vdqor2w0+DVCXu3j13PEyN2KfJm6SiSrWDRMDziiiOUjQ=="}'
'{"sessionId":"0000000000000000000000","data":"8RwaOppCD3uIJVFv2LoP3XGXpomj0xsMv4qmMVy30Vdqor2w0+DVCXu3j13PEyN2KfJm6SiSrWDRMDziiiOUjQ=="}'

We can retrieve an instance of the session, either with the sessionId, or directly with the encrypted message:

js
const retrievedSession = await getSealdSDKInstance().retrieveEncryptionSession({
  encryptedMessage,
  /* OR */
  sessionId: '0000000000000000000000'
})
const retrievedSession = await getSealdSDKInstance().retrieveEncryptionSession({
  encryptedMessage,
  /* OR */
  sessionId: '0000000000000000000000'
})

Once the session is retrieved, it can be used to decrypt a message:

js
const decryptedMessage = await retrievedSession.decryptMessage(encryptedMessage)
const decryptedMessage = await retrievedSession.decryptMessage(encryptedMessage)

You can also add / remove recipients from the session:

javascript
await session.addRecipients({ sealdIds: [user3SealdId] })

await session.revokeRecipients({ sealdIds: [user3SealdId] })
await session.addRecipients({ sealdIds: [user3SealdId] })

await session.revokeRecipients({ sealdIds: [user3SealdId] })

For more details about the sessions, you can consult the dedicated guide.

Integration in a chat room

To integrate these functions in the chat, three elements must be modified:

  • the sending of messages, which must now encrypt for the recipients of the chat;
  • the reception of messages, which must now be decrypted;
  • the management of the members of a chatroom, which must now transpose the members of a chatroom into cryptographic rights.

Encryption upon sending

We have to call the encrypt function each time a message is posted.

In the Chat.jsx file there is a handleSumbitMessage function that we will modify to encrypt:

js
/* frontend/src/components/Chat.jsx */
import { useRef } from 'react'
/* ... */
function Chat ({ roomId: currentRoomId }) {
  /* ... */

  const sealdSessionRef = useRef(null)
  /* ... */
  const handleSubmitMessage = async e => {
    e.preventDefault()
    if (!state.room) {
      enqueueSnackbar('Please select a room or create a new one', { variant: 'error' })
    } else if (state.message.trim()) {
      try {
        // If a session is not known for this chat session, let's create one:
        if (!sealdSessionRef.current) {
          sealdSessionRef.current = await getSealdSDKInstance().createEncryptionSession({ sealdIds: state.room.users.map(u => u.sealdId) }, { metadata: state.room.id })
        }
        // Use this session to encrypt the message
        const encryptedMessage = await sealdSessionRef.current.encryptMessage(state.message)

        // Post the `encryptedMessage` instead of the `message`
        await state.room.postMessage(encryptedMessage)

        setState(draft => {
          draft.message = ''
        })
      } catch (error) {
        console.error(error)
        enqueueSnackbar(error.message, { variant: 'error' })
      }
    }
  }
}
/* frontend/src/components/Chat.jsx */
import { useRef } from 'react'
/* ... */
function Chat ({ roomId: currentRoomId }) {
  /* ... */

  const sealdSessionRef = useRef(null)
  /* ... */
  const handleSubmitMessage = async e => {
    e.preventDefault()
    if (!state.room) {
      enqueueSnackbar('Please select a room or create a new one', { variant: 'error' })
    } else if (state.message.trim()) {
      try {
        // If a session is not known for this chat session, let's create one:
        if (!sealdSessionRef.current) {
          sealdSessionRef.current = await getSealdSDKInstance().createEncryptionSession({ sealdIds: state.room.users.map(u => u.sealdId) }, { metadata: state.room.id })
        }
        // Use this session to encrypt the message
        const encryptedMessage = await sealdSessionRef.current.encryptMessage(state.message)

        // Post the `encryptedMessage` instead of the `message`
        await state.room.postMessage(encryptedMessage)

        setState(draft => {
          draft.message = ''
        })
      } catch (error) {
        console.error(error)
        enqueueSnackbar(error.message, { variant: 'error' })
      }
    }
  }
}

Also, when creating a multi-user chatroom in ManageDialogRoom.jsx a 'Hello 👋' message is sent. It must be encrypted:

js
/* frontend/src/components/ManageDialogRoom.jsx */

/* ... */

const newRoom = await Room.create(
  dialogRoom.name,
  dialogRoom.selectedUsers
)
const sealdSession = await getSealdSDKInstance().createEncryptionSession({ sealdIds: dialogRoom.selectedUsers.map(u => u.sealdId) }, { metadata: newRoom.id })
await newRoom.postMessage(await sealdSession.encryptMessage('Hello 👋'))

/* ... */
/* frontend/src/components/ManageDialogRoom.jsx */

/* ... */

const newRoom = await Room.create(
  dialogRoom.name,
  dialogRoom.selectedUsers
)
const sealdSession = await getSealdSDKInstance().createEncryptionSession({ sealdIds: dialogRoom.selectedUsers.map(u => u.sealdId) }, { metadata: newRoom.id })
await newRoom.postMessage(await sealdSession.encryptMessage('Hello 👋'))

/* ... */

Decryption upon receiving

We need to call the decrypt function each time a message is collected.

To do this we will create a couple of helper functions in Chat.jsx that will be used to use the same data model everywhere:

js
const decrypt = async m => ({
  ...m,
  message: await sealdSessionRef.current.decryptMessage(m.encryptedMessage)
})

const serializeMessage = m => ({
  encryptedMessage: m.content,
  timestamp: m.createdAt,
  senderId: m.senderId,
  id: m.id
})
const decrypt = async m => ({
  ...m,
  message: await sealdSessionRef.current.decryptMessage(m.encryptedMessage)
})

const serializeMessage = m => ({
  encryptedMessage: m.content,
  timestamp: m.createdAt,
  senderId: m.senderId,
  id: m.id
})

Let's modify the eventListener of the event room:messageSent:

js
/* frontend/src/components/Chat.jsx */
useEffect(() => {
  const listener = async payload => {
    const { id, roomId } = payload
    if (currentRoomId === roomId) {
      const message = serializeMessage(payload)
      if (!sealdSessionRef.current) sealdSessionRef.current = await getSealdSDKInstance().retrieveEncryptionSession({ encryptedMessage: message.encryptedMessage })

      const clearMessage = await decrypt(message)

      setState(draft => {
        if (!draft.messages.find(m => m.id === id)) {
          draft.messages = [...draft.messages, clearMessage]
        }
      })
    }
  }
  if (socket) socket.on('room:messageSent', listener)
  return () => { if (socket) socket.off('room:messageSent', listener) }
}, [socket, currentRoomId])
/* frontend/src/components/Chat.jsx */
useEffect(() => {
  const listener = async payload => {
    const { id, roomId } = payload
    if (currentRoomId === roomId) {
      const message = serializeMessage(payload)
      if (!sealdSessionRef.current) sealdSessionRef.current = await getSealdSDKInstance().retrieveEncryptionSession({ encryptedMessage: message.encryptedMessage })

      const clearMessage = await decrypt(message)

      setState(draft => {
        if (!draft.messages.find(m => m.id === id)) {
          draft.messages = [...draft.messages, clearMessage]
        }
      })
    }
  }
  if (socket) socket.on('room:messageSent', listener)
  return () => { if (socket) socket.off('room:messageSent', listener) }
}, [socket, currentRoomId])

We are going to modify the retrieval of the history of the messages at the loading of a room:

js
/* frontend/src/components/Chat.jsx */
const init = async () => {
  // get current room (unchanged)
  const currentRoom = rooms.find(r => r.id === currentRoomId)
  if (currentRoom) {
    try {
      if (!(currentRoom.users.includes(currentUser.id))) {
        /* handle error */
      } else {
        // set initial state (unchanged)
        setState(draft => {
          draft.message = ''
          draft.isCustomRoom = !currentRoom.one2one
          draft.room = currentRoom
          draft.roomTitle = currentRoom.one2one ? users.find(user => currentRoom.users.includes(user.id) && user.id !== currentUser.id)?.name : currentRoom.name
          draft.canCustomizeRoom = !currentRoom.one2one && currentRoom.ownerId === currentUser.id
          draft.users = users.filter(u => currentRoom.users.includes(u.id))
          draft.messages = []
        })
        // Reset stored session when re-initializing
        sealdSessionRef.current = null
        // Get encrypted messages
        const messages = (await currentRoom.getMessages()).map(serializeMessage)
        const clearMessages = []
        if (messages.length) {
          // Retrieve session using the first encryptedMessage
          sealdSessionRef.current = await getSealdSDKInstance().retrieveEncryptionSession({ encryptedMessage: messages[0].encryptedMessage })
          // Decrypt each message
          clearMessages.push(...(await Promise.all(messages.map(decrypt))))
        }
        // Set state
        setState(draft => {
          draft.messages = clearMessages
          draft.isLoading = false
        })
      }
    } catch (error) {
      /* handle error */
    }
  } else {
    /* handle error */
  }
}
/* frontend/src/components/Chat.jsx */
const init = async () => {
  // get current room (unchanged)
  const currentRoom = rooms.find(r => r.id === currentRoomId)
  if (currentRoom) {
    try {
      if (!(currentRoom.users.includes(currentUser.id))) {
        /* handle error */
      } else {
        // set initial state (unchanged)
        setState(draft => {
          draft.message = ''
          draft.isCustomRoom = !currentRoom.one2one
          draft.room = currentRoom
          draft.roomTitle = currentRoom.one2one ? users.find(user => currentRoom.users.includes(user.id) && user.id !== currentUser.id)?.name : currentRoom.name
          draft.canCustomizeRoom = !currentRoom.one2one && currentRoom.ownerId === currentUser.id
          draft.users = users.filter(u => currentRoom.users.includes(u.id))
          draft.messages = []
        })
        // Reset stored session when re-initializing
        sealdSessionRef.current = null
        // Get encrypted messages
        const messages = (await currentRoom.getMessages()).map(serializeMessage)
        const clearMessages = []
        if (messages.length) {
          // Retrieve session using the first encryptedMessage
          sealdSessionRef.current = await getSealdSDKInstance().retrieveEncryptionSession({ encryptedMessage: messages[0].encryptedMessage })
          // Decrypt each message
          clearMessages.push(...(await Promise.all(messages.map(decrypt))))
        }
        // Set state
        setState(draft => {
          draft.messages = clearMessages
          draft.isLoading = false
        })
      }
    } catch (error) {
      /* handle error */
    }
  } else {
    /* handle error */
  }
}

Modifying the rights upon editing a room

When the room is multi-user, the creator of the room can add / remove members. The sessions allow to manage these movements.

To do this, when the creator of the room makes these movements on the room, he must make the same movements on the encryption session.

When a room is modified, the session associated with the room must be given during the dispatch:

js
/* frontend/src/components/Chat.jsx */
const handleEditRoom = () => {
  dispatch({
    type: START_EDIT_DIALOG_ROOM,
    payload: {
      room: state.room,
      name: state.room.name,
      selectedUsersId: state.room.users,
      sealdSession: sealdSessionRef.current
    }
  })
}
/* frontend/src/components/Chat.jsx */
const handleEditRoom = () => {
  dispatch({
    type: START_EDIT_DIALOG_ROOM,
    payload: {
      room: state.room,
      name: state.room.name,
      selectedUsersId: state.room.users,
      sealdSession: sealdSessionRef.current
    }
  })
}

In ManageDialogRoom.jsx before making the API call to the server to actually edit the room:

js
/* frontend/src/components/ManageDialogRoom.jsx  */
if (dialogRoom.room) {
  await dialogRoom.sealdSession.revokeRecipients({ sealdIds: dialogRoom.room.users.filter(u => !dialogRoom.selectedUsers.includes(u.id)).map(u => u.sealdId) })
  await dialogRoom.sealdSession.addRecipients({ sealdIds: dialogRoom.selectedUsers.filter(u => !dialogRoom.room.users.includes(u.id)).map(u => u.sealdId) })
  await dialogRoom.room.edit({ name: dialogRoom.name, users: dialogRoom.selectedUsers })
}
/* frontend/src/components/ManageDialogRoom.jsx  */
if (dialogRoom.room) {
  await dialogRoom.sealdSession.revokeRecipients({ sealdIds: dialogRoom.room.users.filter(u => !dialogRoom.selectedUsers.includes(u.id)).map(u => u.sealdId) })
  await dialogRoom.sealdSession.addRecipients({ sealdIds: dialogRoom.selectedUsers.filter(u => !dialogRoom.room.users.includes(u.id)).map(u => u.sealdId) })
  await dialogRoom.room.edit({ name: dialogRoom.name, users: dialogRoom.selectedUsers })
}

Conclusion

We were able to quickly integrate encryption into this project, but there are still three 3 issues remain before going into production:

  • password pre-derivation;
  • identity persistence when opening a new tab by storing the identity in localstorage;
  • the generation of license tokens from the backend.