Reorganize server files and address TODO comments

Changes:
- Remove unneeded dependencies from package.json
- Remove unneeded async build() methods from services
    - Use constructor as often as possible
- Rename and move storage services for clarity
    - creds.ts -> accounts.ts, and creds.json -> accounts.json
    - admin-config.ts -> admin-account.ts
    - vault.ts -> signing-keys.ts
- Rename ws.ts to websocket-server.ts for clarity and consistency
- Make WebSocketServer initialize using constructor and bind server upgrade to WebSocketServer.upgrade
- Remove unused send-account-message endpoint from account-router.ts
- Set issuer and audience claims for JWT
- Create new utils/jwt.ts file to remove code duplication for JWT signing and verifying
- Delete utils.ts and merge it with jami-swig.ts
- Handle potentially undefined types in jami-swig.ts
- Replace hard to read one-liners with functions in jami-swig.ts
- Rename types in jami-swig.ts for consistency with daemon
- Remove handled/answered TODO comments
- Remove TODO comment about using .env for jamid.node as it does not work for require()

GitLab: #87
Change-Id: I1e5216ffa79ea34dd7e9b61540fb7e37d1f66c9f
diff --git a/server/src/routers/account-router.ts b/server/src/routers/account-router.ts
index 9b72667..505d1d9 100644
--- a/server/src/routers/account-router.ts
+++ b/server/src/routers/account-router.ts
@@ -15,10 +15,9 @@
  * License along with this program.  If not, see
  * <https://www.gnu.org/licenses/>.
  */
-import { Request, Router } from 'express';
+import { Router } from 'express';
 import asyncHandler from 'express-async-handler';
-import { ParamsDictionary } from 'express-serve-static-core';
-import { AccountDetails, AccountTextMessage, HttpStatusCode } from 'jami-web-common';
+import { AccountDetails, HttpStatusCode } from 'jami-web-common';
 import { Container } from 'typedi';
 
 import { Jamid } from '../jamid/jamid.js';
@@ -67,15 +66,3 @@
 
   res.sendStatus(HttpStatusCode.NoContent);
 });
-
-// TODO: Should this endpoint be removed?
-accountRouter.post('/send-account-message', (req: Request<ParamsDictionary, any, AccountTextMessage<unknown>>, res) => {
-  const { from, to, message } = req.body;
-  if (from === undefined || to === undefined || message === undefined) {
-    res.status(HttpStatusCode.BadRequest).send('Missing from, to, or message in body');
-    return;
-  }
-
-  jamid.sendAccountTextMessage(from, to, JSON.stringify(message));
-  res.sendStatus(HttpStatusCode.NoContent);
-});
diff --git a/server/src/routers/auth-router.ts b/server/src/routers/auth-router.ts
index f04529e..4255655 100644
--- a/server/src/routers/auth-router.ts
+++ b/server/src/routers/auth-router.ts
@@ -20,27 +20,25 @@
 import asyncHandler from 'express-async-handler';
 import { ParamsDictionary, Request } from 'express-serve-static-core';
 import { HttpStatusCode } from 'jami-web-common';
-import { SignJWT } from 'jose';
 import { Container } from 'typedi';
 
-import { Creds } from '../creds.js';
 import { Jamid } from '../jamid/jamid.js';
-import { Vault } from '../vault.js';
+import { Accounts } from '../storage/accounts.js';
+import { signJwt } from '../utils/jwt.js';
 
 interface Credentials {
-  username?: string;
-  password?: string;
+  username: string;
+  password: string;
 }
 
 const jamid = Container.get(Jamid);
-const creds = Container.get(Creds);
-const vault = Container.get(Vault);
+const accounts = Container.get(Accounts);
 
 export const authRouter = Router();
 
 authRouter.post(
   '/new-account',
-  asyncHandler(async (req: Request<ParamsDictionary, string, Credentials>, res, _next) => {
+  asyncHandler(async (req: Request<ParamsDictionary, string, Partial<Credentials>>, res, _next) => {
     const { username, password } = req.body;
     if (username === undefined || password === undefined) {
       res.status(HttpStatusCode.BadRequest).send('Missing username or password in body');
@@ -57,12 +55,8 @@
     // TODO: add JAMS support
     // managerUri: 'https://jams.savoirfairelinux.com',
     // managerUsername: data.username,
-    // TODO: find a way to store the password directly in Jami
-    // Maybe by using the "password" field? But as I tested, it's not
-    // returned when getting user infos.
     const accountId = await jamid.addAccount(new Map());
 
-    // TODO: understand why the password arg in this call must be empty
     const state = await jamid.registerUsername(accountId, username, '');
     if (state !== 0) {
       jamid.removeAccount(accountId);
@@ -76,8 +70,8 @@
       return;
     }
 
-    creds.set(username, hashedPassword);
-    await creds.save();
+    accounts.set(username, hashedPassword);
+    await accounts.save();
 
     res.sendStatus(HttpStatusCode.Created);
   })
@@ -85,45 +79,37 @@
 
 authRouter.post(
   '/login',
-  asyncHandler(async (req: Request<ParamsDictionary, { accessToken: string } | string, Credentials>, res, _next) => {
-    const { username, password } = req.body;
-    if (username === undefined || password === undefined) {
-      res.status(HttpStatusCode.BadRequest).send('Missing username or password in body');
-      return;
-    }
+  asyncHandler(
+    async (req: Request<ParamsDictionary, { accessToken: string } | string, Partial<Credentials>>, res, _next) => {
+      const { username, password } = req.body;
+      if (username === undefined || password === undefined) {
+        res.status(HttpStatusCode.BadRequest).send('Missing username or password in body');
+        return;
+      }
 
-    // The account may either be:
-    // 1. not found
-    // 2. found but not on this instance (but I'm not sure about this)
-    const accountId = jamid.getAccountIdFromUsername(username);
-    if (accountId === undefined) {
-      res.status(HttpStatusCode.NotFound).send('Username not found');
-      return;
-    }
+      // Check if the account is stored stored on this daemon instance
+      const accountId = jamid.getAccountIdFromUsername(username);
+      if (accountId === undefined) {
+        res.status(HttpStatusCode.NotFound).send('Username not found');
+        return;
+      }
 
-    // TODO: load the password from Jami
-    const hashedPassword = creds.get(username);
-    if (!hashedPassword) {
-      res
-        .status(HttpStatusCode.NotFound)
-        .send('Password not found (the account does not have a password set on the server)');
-      return;
-    }
+      const hashedPassword = accounts.get(username);
+      if (hashedPassword === undefined) {
+        res
+          .status(HttpStatusCode.NotFound)
+          .send('Password not found (the account does not have a password set on the server)');
+        return;
+      }
 
-    const isPasswordVerified = await argon2.verify(hashedPassword, password);
-    if (!isPasswordVerified) {
-      res.status(HttpStatusCode.Unauthorized).send('Incorrect password');
-      return;
-    }
+      const isPasswordVerified = await argon2.verify(hashedPassword, password);
+      if (!isPasswordVerified) {
+        res.status(HttpStatusCode.Unauthorized).send('Incorrect password');
+        return;
+      }
 
-    const jwt = await new SignJWT({ id: accountId })
-      .setProtectedHeader({ alg: 'EdDSA' })
-      .setIssuedAt()
-      // TODO: use valid issuer and audience
-      .setIssuer('urn:example:issuer')
-      .setAudience('urn:example:audience')
-      .setExpirationTime('2h')
-      .sign(vault.privateKey);
-    res.send({ accessToken: jwt });
-  })
+      const jwt = await signJwt(accountId);
+      res.send({ accessToken: jwt });
+    }
+  )
 );
diff --git a/server/src/routers/setup-router.ts b/server/src/routers/setup-router.ts
index 7dfa24f..3bc3088 100644
--- a/server/src/routers/setup-router.ts
+++ b/server/src/routers/setup-router.ts
@@ -20,20 +20,18 @@
 import asyncHandler from 'express-async-handler';
 import { ParamsDictionary, Request } from 'express-serve-static-core';
 import { HttpStatusCode } from 'jami-web-common';
-import { SignJWT } from 'jose';
 import { Container } from 'typedi';
 
-import { AdminConfig } from '../admin-config.js';
 import { checkAdminSetup } from '../middleware/setup.js';
-import { Vault } from '../vault.js';
+import { AdminAccount } from '../storage/admin-account.js';
+import { signJwt } from '../utils/jwt.js';
+
+const adminAccount = Container.get(AdminAccount);
 
 export const setupRouter = Router();
 
-const vault = Container.get(Vault);
-const adminConfig = Container.get(AdminConfig);
-
 setupRouter.get('/check', (_req, res, _next) => {
-  const isSetupComplete = adminConfig.get() !== undefined;
+  const isSetupComplete = adminAccount.get() !== undefined;
   res.send({ isSetupComplete });
 });
 
@@ -51,7 +49,7 @@
       return;
     }
 
-    const isAdminCreated = adminConfig.get() !== undefined;
+    const isAdminCreated = adminAccount.get() !== undefined;
     if (isAdminCreated) {
       res.status(HttpStatusCode.Conflict).send('Admin already exists');
       return;
@@ -59,8 +57,8 @@
 
     const hashedPassword = await argon2.hash(password, { type: argon2.argon2id });
 
-    adminConfig.set(hashedPassword);
-    await adminConfig.save();
+    adminAccount.set(hashedPassword);
+    await adminAccount.save();
 
     res.sendStatus(HttpStatusCode.Created);
   })
@@ -68,7 +66,7 @@
 
 // Every request handler after this line will be submitted to this middleware
 // in order to ensure that the admin account is set up before proceeding with
-// setup related requests
+// setup-related requests
 setupRouter.use(checkAdminSetup);
 
 setupRouter.post(
@@ -81,7 +79,11 @@
         return;
       }
 
-      const hashedPassword = adminConfig.get();
+      const hashedPassword = adminAccount.get();
+      if (hashedPassword === undefined) {
+        res.status(HttpStatusCode.InternalServerError).send('Admin password not found');
+        return;
+      }
 
       const isPasswordVerified = await argon2.verify(hashedPassword, password);
       if (!isPasswordVerified) {
@@ -89,14 +91,7 @@
         return;
       }
 
-      const jwt = await new SignJWT({ id: 'admin' })
-        .setProtectedHeader({ alg: 'EdDSA' })
-        .setIssuedAt()
-        // TODO: use valid issuer and audience
-        .setIssuer('urn:example:issuer')
-        .setAudience('urn:example:audience')
-        .setExpirationTime('2h')
-        .sign(vault.privateKey);
+      const jwt = await signJwt('admin');
       res.send({ accessToken: jwt });
     }
   )