Create new interfaces for objects transmitted using the REST API
Changes:
- Create new IContact, IAccount, and IConversation interfaces in common/
- These interfaces represent the serialized versions of the models which are transferred
- The client models are classes which implement these interfaces
- Create new LookupResult interface for nameserver lookup results
- Create new IConversationMember interface for conversation members
- The client interface ConversationMember extends this interface to have a Contact field rather than IContact
- Create new ConversationInfos interface for conversation infos
- Create new ContactDetails interface for contact details (used by contacts routes)
- Move request and response body interfaces into common/
- Merge AccountConfig into AccountDetails interface
- Create interfaces for server-only objects:
- ConversationMemberInfos
- ConversationRequestMetadata
- Ensure interfaces in jami-signal-interfaces.ts do not contain fields with JamiSwig types
- Rename models/ filenames to camelCase as they are not components
- Rewrite client models to have proper TypeScript accessors and remove unused getters
- Rewrite how client models are initialized from the serialized interface using .fromInterface static methods
- Make client models implement the interfaces in common/ for consistency
- Remove unneeded _next parameter for Express.js route handlers
- Use Partial<T> for all Express.js request body types on server
- Type all Axios response body types with interfaces
GitLab: #92
Change-Id: I4b2c75ac632ec5d9bf12a874a5ba04467c76fa6d
diff --git a/server/src/routers/account-router.ts b/server/src/routers/account-router.ts
index 505d1d9..78963e7 100644
--- a/server/src/routers/account-router.ts
+++ b/server/src/routers/account-router.ts
@@ -15,9 +15,10 @@
* License along with this program. If not, see
* <https://www.gnu.org/licenses/>.
*/
-import { Router } from 'express';
+import { Request, Router } from 'express';
import asyncHandler from 'express-async-handler';
-import { AccountDetails, HttpStatusCode } from 'jami-web-common';
+import { ParamsDictionary } from 'express-serve-static-core';
+import { AccountDetails, HttpStatusCode, IAccount, IContact } from 'jami-web-common';
import { Container } from 'typedi';
import { Jamid } from '../jamid/jamid.js';
@@ -38,7 +39,7 @@
// Add usernames for default moderators
const defaultModeratorUris = jamid.getDefaultModeratorUris(accountId);
- const namedDefaultModerators = [];
+ const namedDefaultModerators: IContact[] = [];
for (const defaultModeratorUri of defaultModeratorUris) {
const { username } = await jamid.lookupAddress(defaultModeratorUri, accountId);
namedDefaultModerators.push({
@@ -47,17 +48,18 @@
});
}
- res.send({
+ const account: IAccount = {
id: accountId,
details: jamid.getAccountDetails(accountId),
volatileDetails: jamid.getVolatileAccountDetails(accountId),
defaultModerators: namedDefaultModerators,
devices: jamid.getDevices(accountId),
- });
+ };
+ res.send(account);
})
);
-accountRouter.patch('/', (req, res) => {
+accountRouter.patch('/', (req: Request<ParamsDictionary, string, Partial<AccountDetails>>, res) => {
const accountId = res.locals.accountId;
const currentAccountDetails = jamid.getAccountDetails(accountId);
diff --git a/server/src/routers/auth-router.ts b/server/src/routers/auth-router.ts
index db82fd6..21ffefc 100644
--- a/server/src/routers/auth-router.ts
+++ b/server/src/routers/auth-router.ts
@@ -19,19 +19,13 @@
import { Router } from 'express';
import asyncHandler from 'express-async-handler';
import { ParamsDictionary, Request } from 'express-serve-static-core';
-import { AccountDetails, HttpStatusCode } from 'jami-web-common';
+import { AccessToken, AccountDetails, HttpStatusCode, UserCredentials } from 'jami-web-common';
import { Container } from 'typedi';
import { Jamid } from '../jamid/jamid.js';
import { Accounts } from '../storage/accounts.js';
import { signJwt } from '../utils/jwt.js';
-interface Credentials {
- username: string;
- password: string;
- isJams: boolean;
-}
-
const jamid = Container.get(Jamid);
const accounts = Container.get(Accounts);
@@ -39,7 +33,7 @@
authRouter.post(
'/new-account',
- asyncHandler(async (req: Request<ParamsDictionary, string, Partial<Credentials>>, res, _next) => {
+ asyncHandler(async (req: Request<ParamsDictionary, string, Partial<UserCredentials>>, res) => {
const { username, password, isJams } = req.body;
if (username === undefined || password === undefined) {
res.status(HttpStatusCode.BadRequest).send('Missing username or password in body');
@@ -60,7 +54,7 @@
const hashedPassword = await argon2.hash(password, { type: argon2.argon2id });
const accountDetails: Partial<AccountDetails> = {
- // TODO: enable encrypted archives
+ // TODO: Enable encrypted archives
// 'Account.archivePassword': password
};
if (isJams) {
@@ -99,37 +93,35 @@
authRouter.post(
'/login',
- asyncHandler(
- async (req: Request<ParamsDictionary, { accessToken: string } | string, Partial<Credentials>>, res, _next) => {
- const { username, password, isJams } = req.body;
- if (username === undefined || password === undefined) {
- res.status(HttpStatusCode.BadRequest).send('Missing username or password in body');
- 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;
- }
-
- const hashedPassword = accounts.get(username, isJams);
- 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 jwt = await signJwt(accountId);
- res.send({ accessToken: jwt });
+ asyncHandler(async (req: Request<ParamsDictionary, AccessToken | string, Partial<UserCredentials>>, res) => {
+ const { username, password, isJams } = req.body;
+ if (username === undefined || password === undefined) {
+ res.status(HttpStatusCode.BadRequest).send('Missing username or password in body');
+ 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;
+ }
+
+ const hashedPassword = accounts.get(username, isJams);
+ 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 jwt = await signJwt(accountId);
+ res.send({ accessToken: jwt });
+ })
);
diff --git a/server/src/routers/conversation-router.ts b/server/src/routers/conversation-router.ts
index b306bf1..bd3a900 100644
--- a/server/src/routers/conversation-router.ts
+++ b/server/src/routers/conversation-router.ts
@@ -18,24 +18,26 @@
import { Request, Router } from 'express';
import asyncHandler from 'express-async-handler';
import { ParamsDictionary } from 'express-serve-static-core';
-import { HttpStatusCode } from 'jami-web-common';
+import {
+ ContactDetails,
+ HttpStatusCode,
+ IConversation,
+ IConversationMember,
+ NewConversationRequestBody,
+ NewMessageRequestBody,
+} from 'jami-web-common';
import { Container } from 'typedi';
import { Jamid } from '../jamid/jamid.js';
import { authenticateToken } from '../middleware/auth.js';
-interface ConversationMembers {
- members: string[];
-}
-
-interface ConversationMessage {
- message: string;
-}
-
const jamid = Container.get(Jamid);
-// TODO: Create interface for return type in common/ when Records and interfaces are refactored
-async function createConversationResponseObject(accountId: string, accountUri: string, conversationId: string) {
+async function createConversationResponseObject(
+ accountId: string,
+ accountUri: string,
+ conversationId: string
+): Promise<IConversation | undefined> {
const infos = jamid.getConversationInfos(accountId, conversationId);
if (Object.keys(infos).length === 0) {
return undefined;
@@ -43,7 +45,7 @@
const members = jamid.getConversationMembers(accountId, conversationId);
- const namedMembers = [];
+ const namedMembers: IConversationMember[] = [];
for (const member of members) {
// Exclude current user from returned conversation members
if (member.uri === accountUri) {
@@ -51,7 +53,6 @@
}
// Add usernames for conversation members
- // TODO: Add caching in jamid to avoid too many address -> username lookups?
const { username } = await jamid.lookupAddress(member.uri, accountId);
namedMembers.push({
role: member.role,
@@ -68,8 +69,8 @@
return {
id: conversationId,
- messages: messages,
members: namedMembers,
+ messages: messages,
infos: infos,
};
}
@@ -100,7 +101,7 @@
conversationRouter.post(
'/',
- (req: Request<ParamsDictionary, Record<string, string> | string, ConversationMembers>, res) => {
+ (req: Request<ParamsDictionary, ContactDetails | string, Partial<NewConversationRequestBody>>, res) => {
const { members } = req.body;
if (members === undefined || members.length !== 1) {
res.status(HttpStatusCode.BadRequest).send('Missing members or more than one member in body');
@@ -125,7 +126,6 @@
);
// TODO: Check if we actually need this endpoint to return messages.
-// Verify by checking what is truly needed in the client when migrating, to clean up the API.
// At the moment, /conversations does a lot of work returning all the conversations with the same
// level of detail as this, and /conversations/messages returns just the messages. Check whether or not
// this is what we want, and if so, if we can be more economical with client requests.
@@ -167,7 +167,7 @@
conversationRouter.post(
'/:conversationId/messages',
- (req: Request<ParamsDictionary, any, ConversationMessage>, res) => {
+ (req: Request<ParamsDictionary, string, Partial<NewMessageRequestBody>>, res) => {
const { message } = req.body;
if (message === undefined) {
res.status(HttpStatusCode.BadRequest).send('Missing message in body');
diff --git a/server/src/routers/default-moderators-router.ts b/server/src/routers/default-moderators-router.ts
index 06d79ca..90c6387 100644
--- a/server/src/routers/default-moderators-router.ts
+++ b/server/src/routers/default-moderators-router.ts
@@ -17,7 +17,7 @@
*/
import { Router } from 'express';
import asyncHandler from 'express-async-handler';
-import { HttpStatusCode } from 'jami-web-common';
+import { HttpStatusCode, IContact } from 'jami-web-common';
import { Container } from 'typedi';
import { Jamid } from '../jamid/jamid.js';
@@ -36,7 +36,7 @@
// Add usernames for default moderators
const defaultModeratorUris = jamid.getDefaultModeratorUris(accountId);
- const namedDefaultModerators = [];
+ const namedDefaultModerators: IContact[] = [];
for (const defaultModeratorUri of defaultModeratorUris) {
const { username } = await jamid.lookupAddress(defaultModeratorUri, accountId);
namedDefaultModerators.push({
diff --git a/server/src/routers/setup-router.ts b/server/src/routers/setup-router.ts
index 3bc3088..d6f4831 100644
--- a/server/src/routers/setup-router.ts
+++ b/server/src/routers/setup-router.ts
@@ -19,7 +19,7 @@
import { Router } from 'express';
import asyncHandler from 'express-async-handler';
import { ParamsDictionary, Request } from 'express-serve-static-core';
-import { HttpStatusCode } from 'jami-web-common';
+import { AccessToken, AdminCredentials, HttpStatusCode } from 'jami-web-common';
import { Container } from 'typedi';
import { checkAdminSetup } from '../middleware/setup.js';
@@ -30,14 +30,14 @@
export const setupRouter = Router();
-setupRouter.get('/check', (_req, res, _next) => {
+setupRouter.get('/check', (_req, res) => {
const isSetupComplete = adminAccount.get() !== undefined;
res.send({ isSetupComplete });
});
setupRouter.post(
'/admin/create',
- asyncHandler(async (req: Request<ParamsDictionary, string, { password?: string }>, res, _next) => {
+ asyncHandler(async (req: Request<ParamsDictionary, string, Partial<AdminCredentials>>, res) => {
const { password } = req.body;
if (password === undefined) {
res.status(HttpStatusCode.BadRequest).send('Missing password in body');
@@ -71,28 +71,26 @@
setupRouter.post(
'/admin/login',
- asyncHandler(
- async (req: Request<ParamsDictionary, { accessToken: string } | string, { password: string }>, res, _next) => {
- const { password } = req.body;
- if (password === undefined) {
- res.status(HttpStatusCode.BadRequest).send('Missing password in body');
- return;
- }
-
- 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) {
- res.status(HttpStatusCode.Forbidden).send('Incorrect password');
- return;
- }
-
- const jwt = await signJwt('admin');
- res.send({ accessToken: jwt });
+ asyncHandler(async (req: Request<ParamsDictionary, AccessToken | string, Partial<AdminCredentials>>, res) => {
+ const { password } = req.body;
+ if (password === undefined) {
+ res.status(HttpStatusCode.BadRequest).send('Missing password in body');
+ return;
}
- )
+
+ 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) {
+ res.status(HttpStatusCode.Forbidden).send('Incorrect password');
+ return;
+ }
+
+ const jwt = await signJwt('admin');
+ res.send({ accessToken: jwt });
+ })
);