Create nameserver API routes
Change-Id: I1f5400556eb556d86273c8662b03e8cd5406a61c
diff --git a/server/src/app.ts b/server/src/app.ts
index 53c8c23..161711c 100644
--- a/server/src/app.ts
+++ b/server/src/app.ts
@@ -23,6 +23,7 @@
import { accountRouter } from './routers/account-router.js';
import { authRouter } from './routers/auth-router.js';
+import { nameserverRouter } from './routers/nameserver-router.js';
@Service()
export class App {
@@ -36,6 +37,7 @@
// Setup routing
app.use('/auth', authRouter);
app.use('/account', accountRouter);
+ app.use('/ns', nameserverRouter);
// Setup 404 error handling
app.use((_req, res) => {
diff --git a/server/src/jamid/jamid.ts b/server/src/jamid/jamid.ts
index a865777..ab1963f 100644
--- a/server/src/jamid/jamid.ts
+++ b/server/src/jamid/jamid.ts
@@ -17,7 +17,7 @@
*/
import { AccountDetails, VolatileDetails } from 'jami-web-common';
import log from 'loglevel';
-import { filter, firstValueFrom, Subject } from 'rxjs';
+import { filter, firstValueFrom, map, Subject } from 'rxjs';
import { Service } from 'typedi';
import { JamiSignal } from './jami-signal.js';
@@ -133,8 +133,9 @@
const accountId = this.jamiSwig.addAccount(detailsStringMap);
return firstValueFrom(
this.events.onRegistrationStateChanged.pipe(
- filter(({ accountId: addedAccountId }) => addedAccountId === accountId),
+ filter((value) => value.accountId === accountId),
// TODO: is it the only state?
+ // TODO: Replace with string enum in common/
filter(({ state }) => state === 'REGISTERED')
)
);
@@ -154,26 +155,38 @@
this.jamiSwig.sendAccountTextMessage(accountId, contactId, messageStringMap);
}
- async lookupUsername(username: string) {
- const hasRingNs = this.jamiSwig.lookupName('', '', username);
+ async lookupUsername(username: string, accountId?: string) {
+ const hasRingNs = this.jamiSwig.lookupName(accountId || '', '', username);
if (!hasRingNs) {
- log.error('Jami does not have NS');
throw new Error('Jami does not have NS');
}
- return firstValueFrom(this.events.onRegisteredNameFound.pipe(filter((r) => r.username === username)));
+ return firstValueFrom(
+ this.events.onRegisteredNameFound.pipe(
+ filter((value) => value.username === username),
+ map(({ accountId: _, ...response }) => response) // Remove accountId from response
+ )
+ );
+ }
+
+ async lookupAddress(address: string, accountId?: string) {
+ const hasRingNs = this.jamiSwig.lookupAddress(accountId || '', '', address);
+ if (!hasRingNs) {
+ throw new Error('Jami does not have NS');
+ }
+ return firstValueFrom(
+ this.events.onRegisteredNameFound.pipe(
+ filter((value) => value.address === address),
+ map(({ accountId: _, ...response }) => response) // Remove accountId from response
+ )
+ );
}
async registerUsername(accountId: string, username: string, password: string) {
const hasRingNs = this.jamiSwig.registerName(accountId, password, username);
if (!hasRingNs) {
- log.error('Jami does not have NS');
throw new Error('Jami does not have NS');
}
- return firstValueFrom(
- this.events.onNameRegistrationEnded.pipe(
- filter(({ accountId: registeredAccountId }) => registeredAccountId === accountId)
- )
- );
+ return firstValueFrom(this.events.onNameRegistrationEnded.pipe(filter((value) => value.accountId === accountId)));
}
getDevices(accountId: string): Record<string, string> {
diff --git a/server/src/middleware/auth.ts b/server/src/middleware/auth.ts
index c6992d3..70eca75 100644
--- a/server/src/middleware/auth.ts
+++ b/server/src/middleware/auth.ts
@@ -22,29 +22,41 @@
import { Vault } from '../vault.js';
-export async function authenticateToken(req: Request, res: Response, next: NextFunction) {
- const publicKey = Container.get(Vault).publicKey;
+function createAuthenticationMiddleware(isAuthenticationRequired: boolean) {
+ return async (req: Request, res: Response, next: NextFunction) => {
+ const publicKey = Container.get(Vault).publicKey;
- const authorizationHeader = req.headers.authorization;
- if (!authorizationHeader) {
- res.status(HttpStatusCode.Unauthorized).send('Missing Authorization header');
- return;
- }
+ const authorizationHeader = req.headers.authorization;
+ if (!authorizationHeader) {
+ if (isAuthenticationRequired) {
+ res.status(HttpStatusCode.Unauthorized).send('Missing Authorization header');
+ } else {
+ // Skip authentication if it is optional, in which case the Authorization header should not have been set
+ res.locals.accountId = undefined;
+ next();
+ }
+ return;
+ }
- const token = authorizationHeader.split(' ')[1];
- if (token === undefined) {
- res.status(HttpStatusCode.BadRequest).send('Missing JSON web token');
- return;
- }
+ const token = authorizationHeader.split(' ')[1];
+ if (token === undefined) {
+ res.status(HttpStatusCode.BadRequest).send('Missing JSON web token');
+ return;
+ }
- try {
- const { payload } = await jwtVerify(token, publicKey, {
- issuer: 'urn:example:issuer',
- audience: 'urn:example:audience',
- });
- res.locals.accountId = payload.id as string;
- next();
- } catch (err) {
- res.sendStatus(HttpStatusCode.Unauthorized);
- }
+ try {
+ const { payload } = await jwtVerify(token, publicKey, {
+ issuer: 'urn:example:issuer',
+ audience: 'urn:example:audience',
+ });
+ res.locals.accountId = payload.id as string;
+ next();
+ } catch (err) {
+ res.sendStatus(HttpStatusCode.Unauthorized);
+ }
+ };
}
+
+export const authenticateToken = createAuthenticationMiddleware(true);
+
+export const authenticateOptionalToken = createAuthenticationMiddleware(false);
diff --git a/server/src/routers/nameserver-router.ts b/server/src/routers/nameserver-router.ts
new file mode 100644
index 0000000..6e49b52
--- /dev/null
+++ b/server/src/routers/nameserver-router.ts
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program. If not, see
+ * <https://www.gnu.org/licenses/>.
+ */
+import { Router } from 'express';
+import asyncHandler from 'express-async-handler';
+import { HttpStatusCode } from 'jami-web-common';
+import { Container } from 'typedi';
+
+import { Jamid } from '../jamid/jamid.js';
+import { authenticateOptionalToken } from '../middleware/auth.js';
+
+const jamid = Container.get(Jamid);
+
+export const nameserverRouter = Router();
+
+nameserverRouter.use(authenticateOptionalToken);
+
+nameserverRouter.get(
+ '/username/:username',
+ asyncHandler(async (req, res) => {
+ const result = await jamid.lookupUsername(req.params.username, res.locals.accountId);
+ switch (result.state) {
+ case 0:
+ res.json(result);
+ break;
+ case 1:
+ res.sendStatus(HttpStatusCode.BadRequest);
+ break;
+ default:
+ res.sendStatus(HttpStatusCode.NotFound);
+ break;
+ }
+ })
+);
+
+nameserverRouter.get(
+ '/address/:address',
+ asyncHandler(async (req, res) => {
+ const result = await jamid.lookupAddress(req.params.address, res.locals.accountId);
+ switch (result.state) {
+ case 0:
+ res.json(result);
+ break;
+ case 1:
+ res.sendStatus(HttpStatusCode.BadRequest);
+ break;
+ default:
+ res.sendStatus(HttpStatusCode.NotFound);
+ break;
+ }
+ })
+);