Skip to content

Commit

Permalink
Merge pull request #1771 from serlo/staging
Browse files Browse the repository at this point in the history
Deployment
  • Loading branch information
hugotiburtino authored Oct 17, 2024
2 parents 837dc8e + e4ff53c commit 6b226b3
Show file tree
Hide file tree
Showing 7 changed files with 356 additions and 396 deletions.
26 changes: 12 additions & 14 deletions __tests__/internals/kratos-middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { Server } from 'http'
import { bypass } from 'msw'
import { createPool } from 'mysql2/promise'

import { Identity, Kratos } from '~/context/auth-services'
import { Kratos } from '~/context/auth-services'
import { applyKratosMiddleware } from '~/internals/server/kratos-middleware'

const port = 8100
Expand Down Expand Up @@ -32,19 +32,6 @@ describe('Kratos middleware - register endpoint', () => {
})

test('successful if it finds an account to sync', async () => {
// eslint-disable-next-line @typescript-eslint/unbound-method
kratosMock.db.executeSingleQuery<Identity> = async () => {
return Promise.resolve([
{
id: '23af75f5-009a-4a11-a9d0-d79ac8bc8d34',
traits: {
username: 'serlo-user',
email: 'email@serlo.org',
},
},
] as Identity[])
}

const response = await fetchKratosRegister({})

expect(response.status).toBe(200)
Expand Down Expand Up @@ -189,6 +176,17 @@ function createKratosMock() {
getIdByCredentialIdentifier: async () => {
return Promise.resolve('23af75f5-009a-4a11-a9d0-d79ac8bc8d34')
},
executeSingleQuery: async () => {
return Promise.resolve([
{
id: '23af75f5-009a-4a11-a9d0-d79ac8bc8d34',
traits: {
username: 'serlo-user',
email: 'email@serlo.org',
},
},
])
},
},
} as unknown as Kratos
}
6 changes: 1 addition & 5 deletions enmeshed/config-dist.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,13 @@
"enabled": true,
"interval": 5
},
"autoAcceptRelationshipCreationChanges": {
"enabled": false,
"responseContent": {}
},
"coreHttpApi": {
"enabled": true,
"docs": {
"enabled": false
}
},
"webhooksV2": {
"webhooks": {
"enabled": true,
"webhooks": [
{
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
"@luckycatfactory/esbuild-graphql-loader": "^3.8.1",
"@tsconfig/node20": "^20.1.4",
"@types/jest": "^29.5.12",
"@types/node": "^18.19.50",
"@types/node": "^20.14.8",
"@typescript-eslint/eslint-plugin": "^8.6.0",
"@typescript-eslint/parser": "^8.6.0",
"default-import": "^2.0.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"@graphql-codegen/cli": "^5.0.2",
"@graphql-codegen/typescript": "^4.0.9",
"@graphql-codegen/typescript-resolvers": "^4.2.1",
"@nmshd/connector-sdk": "^4.6.4",
"@nmshd/connector-sdk": "^6.1.2",
"@ory/client": "^1.14.5",
"@sentry/node": "^8.28.0",
"@serlo/authorization": "^0.60.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/error-event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export function assertAll(
export function captureErrorEvent(event: ErrorEvent) {
if (process.env.ENVIRONMENT === 'local') {
// eslint-disable-next-line no-console
console.error(event.error)
console.error(event)
}

Sentry.captureException(event.error, (scope) => {
Expand Down
149 changes: 50 additions & 99 deletions packages/server/src/internals/server/enmeshed-middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,8 @@
*/
import {
ConnectorClient,
ConnectorError,
ConnectorRelationshipChangeStatus,
ConnectorRelationshipChangeType,
ConnectorRequestContent,
ConnectorRelationshipAuditLogEntryReason,
ConnectorRelationshipStatus,
} from '@nmshd/connector-sdk'
import crypto from 'crypto'
import express, { Express, RequestHandler, Request, Response } from 'express'
Expand Down Expand Up @@ -60,9 +58,10 @@ export function applyEnmeshedMiddleware({
return `${basePath}/init`
}

const GenericEventBody = t.type({
trigger: t.string,
})
// we had to shadow this type since the library doesn't provide it yet
enum RelationshipAttributeConfidentiality {
Public = 'public',
}

const Relationship = t.type({
id: t.string,
Expand All @@ -76,7 +75,13 @@ const Relationship = t.type({
}),
}),
}),
changes: t.array(t.type({ type: t.string, status: t.string, id: t.string })),
auditLog: t.array(
t.type({
reason: t.string,
newStatus: t.string,
}),
),
creationContent: t.unknown,
})

type Relationship = t.TypeOf<typeof Relationship>
Expand Down Expand Up @@ -152,6 +157,7 @@ function createEnmeshedInitMiddleware(
})
}

const { owner } = createAttributeResponse.result.content
const requestGroup = {
'@type': 'RequestItemGroup',
mustBeAccepted: true,
Expand All @@ -161,7 +167,7 @@ function createEnmeshedInitMiddleware(
'@type': 'CreateAttributeRequestItem',
mustBeAccepted: true,
attribute: {
owner: '',
owner,
key: 'LernstandMathe',
confidentiality: 'public',
'@type': 'RelationshipAttribute',
Expand All @@ -173,10 +179,10 @@ function createEnmeshedInitMiddleware(
},
},
],
} as ConnectorRequestContent['items'][number]
}

// TODO: Handle privacy See https://github.com/serlo/api.serlo.org/blob/83db29db4a98f6b32c389a0a0f89612fb9f760f8/packages/server/src/internals/server/enmeshed-middleware.ts#L470
const attributesContent: ConnectorRequestContent = {
const attributesContent = {
'@type': 'Request' as const,
metadata: { sessionId: sessionId },
items: [
{
Expand All @@ -189,7 +195,7 @@ function createEnmeshedInitMiddleware(
mustBeAccepted: true,
attribute: {
'@type': 'IdentityAttribute',
owner: '',
owner,
value: {
'@type': 'DisplayName',
value: 'LENABI Demo',
Expand All @@ -208,9 +214,11 @@ function createEnmeshedInitMiddleware(
content: attributesContent,
},
)
if (validationResponse.isError) {

if (!validationResponse.result.isSuccess) {
const { code, message } = validationResponse.result
return handleConnectorError({
error: validationResponse.error,
error: { code, message },
message: 'Error occurred while validating attributes',
response: res,
})
Expand Down Expand Up @@ -335,7 +343,7 @@ function createSetAttributesHandler(
attribute: {
key: name,
owner: '',
confidentiality: 'public',
confidentiality: RelationshipAttributeConfidentiality.Public,
'@type': 'RelationshipAttribute',
value: {
'@type': 'ProprietaryString',
Expand Down Expand Up @@ -418,7 +426,7 @@ function createEnmeshedWebhookMiddleware(

const body = req.body as unknown

if (!GenericEventBody.is(body)) {
if (!t.type({ trigger: t.string }).is(body)) {
res.status(400).send('Illegal trigger body')
return
}
Expand All @@ -434,9 +442,13 @@ function createEnmeshedWebhookMiddleware(
if (!EventBody.is(body)) {
captureErrorEvent({
error: new Error('Illegal body event'),
errorContext: { body, route: '/enmeshed/webhook' },
errorContext: {
body,
validationError: EventBody.decode(body),
route: '/enmeshed/webhook',
},
})
res.status(400).send('Illegal trigger body')
res.status(400).send('Illegal body event')
return
}

Expand All @@ -446,19 +458,27 @@ function createEnmeshedWebhookMiddleware(
const sessionId =
data.template.content?.onNewRelationship?.metadata?.sessionId ?? null

for (const change of data.changes) {
for (const auditLogEntry of data.auditLog) {
if (
[ConnectorRelationshipChangeType.CREATION as string].includes(
change.type,
) &&
(ConnectorRelationshipAuditLogEntryReason.Creation as string) ===
auditLogEntry.reason &&
[
ConnectorRelationshipChangeStatus.PENDING as string,
ConnectorRelationshipChangeStatus.REJECTED as string,
].includes(change.status)
ConnectorRelationshipStatus.Pending as string,
ConnectorRelationshipStatus.Rejected as string,
].includes(auditLogEntry.newStatus)
) {
await acceptRelationshipRequest(data, change, client)
if (data.status !== (ConnectorRelationshipStatus.Active as string)) {
const acceptRelationshipResponse =
await client.relationships.acceptRelationship(data.id)
if (acceptRelationshipResponse.isError) {
handleConnectorError({
error: acceptRelationshipResponse.error,
message: 'Failed while accepting relationship request',
})
}
}

if (!sessionId) {
await sendWelcomeMessage({ relationship: data, client })
await sendAttributesChangeRequest({ relationship: data, client })
}
}
Expand Down Expand Up @@ -491,75 +511,6 @@ function createEnmeshedWebhookMiddleware(
}
}

/**
* Accepts pending relationship request
*/
async function acceptRelationshipRequest(
relationship: Relationship,
change: { id: string },
client: ConnectorClient,
): Promise<void> {
const acceptRelationshipResponse =
await client.relationships.acceptRelationshipChange(
relationship.id,
change.id,
)
if (acceptRelationshipResponse.isError) {
handleConnectorError({
error: acceptRelationshipResponse.error,
message: 'Failed while accepting relationship request',
})
}
}

/**
* Sends a welcome message with a test file attachment to be saved within the users' data wallet
*/
async function sendWelcomeMessage({
relationship,
client,
}: {
relationship: Relationship
client: ConnectorClient
}): Promise<void> {
const expiresAt = new Date()
expiresAt.setHours(expiresAt.getHours() + 1)
const uploadFileResponse = await client.files.uploadOwnFile({
title: 'Serlo Testdatei',
description: 'Test file created by Serlo',
file: Buffer.from(
'<html><head><title>Serlo Testdatei</title></head><body><p>Hello World! - Dies ist eine Testdatei.</p></body></html>',
),
filename: 'serlo-test.html',
expiresAt: expiresAt.toISOString(),
})

if (uploadFileResponse.isError) {
handleConnectorError({
error: uploadFileResponse.error,
message: 'Failed to upload file in welcome message',
})
}

const sendMessageResponse = await client.messages.sendMessage({
recipients: [relationship.peer],
content: {
'@type': 'Mail',
to: [relationship.peer],
subject: 'Danke für dein Vertrauen.',
body: 'Hallo!\nDanke für deine Anfrage, wir freuen uns über dein Vertrauen.\nDein Serlo-Team',
},
attachments: [uploadFileResponse.result.id],
})

if (sendMessageResponse.isError) {
handleConnectorError({
error: sendMessageResponse.error,
message: 'Failed to upload file in welcome message',
})
}
}

/**
* Requests user to change and share attributes in data wallet
* Attributes will be sent to connector webhook after confirmation
Expand All @@ -580,7 +531,7 @@ async function sendAttributesChangeRequest({
attribute: {
key: 'LernstandMathe',
owner: '',
confidentiality: 'public',
confidentiality: RelationshipAttributeConfidentiality.Public,
'@type': 'RelationshipAttribute',
value: {
'@type': 'ProprietaryString',
Expand Down Expand Up @@ -636,7 +587,7 @@ function handleConnectorError({
message,
response,
}: {
error: ConnectorError
error: { code: string | undefined; message: string | undefined }
message: string
response?: Response
}) {
Expand Down
Loading

0 comments on commit 6b226b3

Please sign in to comment.