Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,9 @@ describe('runProcess', () => {
[
"✅ - Module path of "putTime" found at "file:///project/src/routes/putTime.ts".",
],
[
"✅ - Module path of "tokenStore" found at "file:///project/src/services/tokenStore.ts".",
],
[
"✅ - Module path of "wrapRouteHandlerWithAuthorization" found at "@whook/authorization/dist/wrappers/wrapRouteHandlerWithAuthorization.js".",
],
Expand Down Expand Up @@ -1098,6 +1101,9 @@ describe('runProcess', () => {
[
"🍀 - Trying to find "putTime" module path in "__project__".",
],
[
"🍀 - Trying to find "tokenStore" module path in "__project__".",
],
[
"🍀 - Trying to find "uniqueId" module path in "@whook/authorization".",
],
Expand Down Expand Up @@ -1200,6 +1206,9 @@ describe('runProcess', () => {
[
"💿 - Loading "putTime" initializer from "file:///project/src/routes/putTime.ts".",
],
[
"💿 - Loading "tokenStore" initializer from "file:///project/src/services/tokenStore.ts".",
],
[
"💿 - Loading "wrapRouteHandlerWithAuthorization" initializer from "@whook/authorization/dist/wrappers/wrapRouteHandlerWithAuthorization.js".",
],
Expand Down Expand Up @@ -1266,6 +1275,9 @@ describe('runProcess', () => {
[
"💿 - Service "putTime" found in "file:///project/src/routes/putTime.ts".",
],
[
"💿 - Service "tokenStore" found in "file:///project/src/services/tokenStore.ts".",
],
[
"💿 - Service "wrapRouteHandlerWithAuthorization" found in "@whook/authorization/dist/wrappers/wrapRouteHandlerWithAuthorization.js".",
],
Expand Down Expand Up @@ -1815,6 +1827,9 @@ describe('runProcess', () => {
[
"🛂 - Dynamic import of "file:///project/src/services/jwtToken.ts".",
],
[
"🛂 - Dynamic import of "file:///project/src/services/tokenStore.ts".",
],
[
"🛂 - Dynamic import of "swagger-ui-dist".",
],
Expand Down
4 changes: 2 additions & 2 deletions src/openAPISchema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ declare interface operations {
};
getKnockValidation: {
responses: {
200: {
201: {
body: object;
};
};
Expand All @@ -80,7 +80,7 @@ declare interface operations {
putKnockValidation: {
requestBody: object;
responses: {
200: {
201: {
body: object;
};
};
Expand Down
2 changes: 1 addition & 1 deletion src/routes/getKnockValidation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const definition = {
tags: ['system'],
parameters: baseDefinition.operation.parameters,
responses: {
200: {
201: {
description: 'Success',
content: {
'application/json': {
Expand Down
71 changes: 48 additions & 23 deletions src/routes/putKnockValidation.test.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,68 @@
import { describe, test, beforeEach, jest, expect } from '@jest/globals';
import initPutKnockValidation from './putKnockValidation.js';
import streamtest from 'streamtest';
import { type LogService } from 'common-services';
import type { TokenStoreService } from '../services/tokenStore.js';

describe('putKnockValidation', () => {
const log = jest.fn<LogService>();
const tokenStore: jest.Mocked<TokenStoreService> = {
get: jest.fn(),
set: jest.fn(),
};

beforeEach(() => {
log.mockReset();
tokenStore.get.mockReset();
tokenStore.set.mockReset();
});

test('should work', async () => {
test('should validate an existing knock', async () => {
tokenStore.get.mockResolvedValue({
pattern: 'test@example.com',
validated: false,
});

const putKnockValidation = await initPutKnockValidation({
log,
tokenStore,
});

const response = await putKnockValidation({
path: {
knockId: 'rtt',
},
path: { knockId: 'rtt' },
body: {},
});

expect(response).toEqual({
status: 201,
headers: {},
body: {},
});

expect({
response,
logCalls: log.mock.calls.filter(([type]) => !type.endsWith('stack')),
}).toMatchInlineSnapshot(`
{
"logCalls": [
[
"warning",
"📢 - Validated knock: rtt!",
],
],
"response": {
"body": {},
"headers": {},
"status": 200,
},
}
`);
expect(tokenStore.set).toHaveBeenCalledWith('rtt', {
pattern: 'test@example.com',
validated: true,
});

expect(log.mock.calls).toEqual([['warning', '📢 - Validated knock: rtt!']]);
});

test('should throw if knock does not exist', async () => {
tokenStore.get.mockResolvedValue(undefined);

const putKnockValidation = await initPutKnockValidation({
log,
tokenStore,
});

await expect(
putKnockValidation({
path: { knockId: 'unknown' },
body: {},
}),
).rejects.toThrow('E_UNKNOWN_KNOCK');

expect(log.mock.calls).toEqual([
['warning', '❗ - Cannot validate knock: unknown!'],
]);
});
});
34 changes: 28 additions & 6 deletions src/routes/putKnockValidation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ import {
} from '@whook/whook';
import { type LogService } from 'common-services';

import type {
TokenStoreService,
TokenPayload,
} from '../services/tokenStore.js';
import { YHTTPError } from 'yhttperror';

export const definition = {
path: '/knock/{knockId}/validation',
method: 'put',
Expand All @@ -31,7 +37,7 @@ export const definition = {
},
},
responses: {
200: {
201: {
description: 'Successfully validated',
content: {
'application/json': {
Expand All @@ -45,19 +51,35 @@ export const definition = {
},
} as const satisfies WhookRouteDefinition;

async function initPutKnockValidation({ log }: { log: LogService }) {
async function initPutKnockValidation({
log,
tokenStore,
}: {
log: LogService;
tokenStore: TokenStoreService;
}) {
const handler: WhookRouteTypedHandler<
operations[typeof definition.operation.operationId],
typeof definition
> = async ({ path: { knockId }, body }) => {
// inject the token store (see the smtpServer service)
// get the pqyloqd from store set validated to true
// put the payload back to the store
const payload = await tokenStore.get(knockId);

if (!payload) {
log('warning', `❗ - Cannot validate knock: ${knockId}!`);
throw new YHTTPError(404, 'E_UNKNOWN_KNOCK', knockId);
}

const updatedPayload: TokenPayload = {
...payload,
validated: true,
};

await tokenStore.set(knockId, updatedPayload);

log('warning', `📢 - Validated knock: ${knockId}!`);

return {
status: 200,
status: 201,
headers: {},
body: {},
};
Expand Down
4 changes: 2 additions & 2 deletions src/services/__snapshots__/API.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,7 @@ exports[`API should work 1`] = `
},
],
"responses": {
"200": {
"201": {
"content": {
"application/json": {
"schema": {
Expand Down Expand Up @@ -566,7 +566,7 @@ exports[`API should work 1`] = `
"required": true,
},
"responses": {
"200": {
"201": {
"content": {
"application/json": {
"schema": {
Expand Down
23 changes: 19 additions & 4 deletions src/services/smtpServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,16 +86,31 @@ async function initSmtpServer({
`📧 - Email details: from=${fromAddress}, to=${toAddress}, subject=${subject} (session: ${session.id}).`,
);

const token = toAddress.split('@')[0].split('+').pop();
const token = toAddress.split('@')[0].includes('+')
? toAddress.split('@')[0].split('+').pop()
: undefined;

if (!token) {
log(
'warning',
`💌 - Rejected mail from ${fromAddress} since no token (session: ${session.id}).`,
);
return callback(
Object.assign(new Error('Relay denied'), { responseCode: 553 }),
);
await sendMail({
from: toAddress,
to: fromAddress,
subject: 'Protected mailbox',
text: `Hi!

This mailbox is protected by SafeSend, to send emails to it,
you first need to send a knock email to: ${toAddress.split('@')[0]}+knock@${toAddress.split('@')[1]}

Below is a copy of your original email:
Subject: ${subject},
Content:
${text}
`,
});
return callback();
}

if (token === 'knock') {
Expand Down
Loading