Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deployment #1771

Merged
merged 30 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
f1644ec
chore(deps-dev): bump @types/jest from 29.5.12 to 29.5.13
dependabot[bot] Oct 7, 2024
301b28b
chore(deps-dev): bump @nmshd/connector-sdk from 4.6.4 to 6.0.2
dependabot[bot] Oct 7, 2024
2e52085
chore(deps-dev): bump @typescript-eslint/eslint-plugin
dependabot[bot] Oct 7, 2024
24b26f3
chore(deps-dev): bump eslint-plugin-react from 7.36.1 to 7.37.1
dependabot[bot] Oct 7, 2024
74d24cf
Merge pull request #1763 from serlo/dependabot/npm_and_yarn/types/jes…
hugotiburtino Oct 8, 2024
310c530
Merge pull request #1766 from serlo/dependabot/npm_and_yarn/eslint-pl…
hugotiburtino Oct 8, 2024
6ae8c5b
refactor(enmeshed): avoid var that used only once
hugotiburtino Oct 11, 2024
d59a7d6
refactor!(enmeshed): update to v6 doing educated guesses regarding types
hugotiburtino Oct 11, 2024
c973bce
chore(enmeshed): remove deprecated config entry by default disabled
hugotiburtino Oct 11, 2024
882e055
chore(enmeshed): update deprecated config field
hugotiburtino Oct 11, 2024
1645084
Merge pull request #1765 from serlo/dependabot/npm_and_yarn/typescrip…
hugotiburtino Oct 11, 2024
5fa4345
chore(deps-dev): bump express from 4.21.0 to 4.21.1
dependabot[bot] Oct 14, 2024
5591a52
chore(deps-dev): bump @graphql-codegen/cli from 5.0.2 to 5.0.3
dependabot[bot] Oct 14, 2024
f834a2c
Merge pull request #1769 from serlo/dependabot/npm_and_yarn/express-4…
hugotiburtino Oct 14, 2024
aa918c9
Merge pull request #1770 from serlo/dependabot/npm_and_yarn/graphql-c…
hugotiburtino Oct 14, 2024
4786950
chore(deps-dev): bump typescript from 5.5.4 to 5.6.3
dependabot[bot] Oct 14, 2024
0ed2e89
test(kratos): avoid eslint error
hugotiburtino Oct 14, 2024
3f6c477
chore(node): upgrade node types to v20
hugotiburtino Oct 14, 2024
c317b57
test(kratos): remove unused import
hugotiburtino Oct 14, 2024
10972fa
Merge pull request #1767 from serlo/dependabot/npm_and_yarn/typescrip…
hugotiburtino Oct 14, 2024
1046e6a
refactor(enmeshed): handle validation error correctly
hugotiburtino Oct 14, 2024
b1fd528
fix(enmeshed): add real owner init function
hugotiburtino Oct 14, 2024
8fba0ef
Merge pull request #1764 from serlo/dependabot/npm_and_yarn/nmshd/con…
hugotiburtino Oct 14, 2024
9a62ca0
chore(enmeshed): upgrade lib
hugotiburtino Oct 14, 2024
d617566
refactor(error-handling): log whole event locally
hugotiburtino Oct 16, 2024
61f19cf
fix(enmeshed): fix webhook after upgrading lib
hugotiburtino Oct 16, 2024
6c102f5
fix(enmeshed): avoid error when accepting an already active relationship
hugotiburtino Oct 16, 2024
7deae66
Merge pull request #1772 from serlo/enmeshed-adjusts
hugotiburtino Oct 16, 2024
e5b5c2c
refactor(enmeshed): remove welcome message since it is broken and
hugotiburtino Oct 16, 2024
6ed20b6
Merge pull request #1773 from serlo/enmeshed-remove-welcome
hugotiburtino Oct 16, 2024
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
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
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