Skip to content

Commit

Permalink
Implement endpoint to get info about subscriptions of current user
Browse files Browse the repository at this point in the history
Also write tests for it
  • Loading branch information
mrkvon committed Sep 25, 2023
1 parent f674352 commit 89d2210
Show file tree
Hide file tree
Showing 9 changed files with 247 additions and 35 deletions.
4 changes: 4 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# TODO

- re-subscribe when subscription expires
- make nice email messages
2 changes: 2 additions & 0 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
finishIntegration,
initializeIntegration,
} from './controllers/integration'
import { getStatus } from './controllers/status'
import { webhookReceiver } from './controllers/webhookReceiver'
import { solidAuth } from './middlewares/solidAuth'

Expand All @@ -22,6 +23,7 @@ router
.post('/inbox', solidAuth, initializeIntegration)
.get('/verify-email', checkVerificationLink, finishIntegration)
.post('/webhook-receiver', webhookReceiver)
.get('/status', solidAuth, getStatus)

app
.use(helmet())
Expand Down
27 changes: 27 additions & 0 deletions src/controllers/status.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Middleware } from 'koa'
import { EmailVerification, Integration } from '../config/sequelize'

export const getStatus: Middleware = async ctx => {
const webId = ctx.state.user

const verified = await Integration.findAll({ where: { webId } })

const unverified = await EmailVerification.findAll({ where: { webId } })

ctx.response.body = {
actor: webId,
integrations: [
...verified.map(s => ({
object: s.inbox,
target: s.email,
verified: true,
})),
...unverified.map(s => ({
object: s.inbox,
target: s.email,
verified: false,
})),
],
}
ctx.response.status = 200
}
57 changes: 57 additions & 0 deletions src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import {
createDpopHeader,
generateDpopKeyPair,
} from '@inrupt/solid-client-authn-core'
import { expect } from 'chai'
import * as uuid from 'uuid'

type Credentials = { email: string; password: string }

Expand Down Expand Up @@ -55,3 +57,58 @@ export const getAuthenticatedFetch = async ({

return authFetch
}

export const createAccount = async ({
username,
password,
email,
solidServer,
}: {
username: string
password?: string
email?: string
solidServer: string
}) => {
password ??= 'correcthorsebatterystaple'
email ??= username + '@example.org'
const config = {
idp: solidServer + '/',
podUrl: `${solidServer}/${username}/`,
webId: `${solidServer}/${username}/profile/card#me`,
username,
password,
email,
}
const registerEndpoint = solidServer + '/idp/register/'
const response = await fetch(registerEndpoint, {
method: 'post',
body: JSON.stringify({
email,
password,
confirmPassword: password,
createWebId: true,
register: true,
createPod: true,
rootPod: false,
podName: username,
}),
headers: { 'content-type': 'application/json' },
})

expect(response.ok).to.be.true

return config
}

export const createRandomAccount = ({
solidServer,
}: {
solidServer: string
}) => {
return createAccount({
username: uuid.v4(),
password: uuid.v4(),
email: uuid.v4() + '@example.com',
solidServer,
})
}
6 changes: 3 additions & 3 deletions src/test/integration-finish.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Mail from 'nodemailer/lib/mailer'
import { SinonSandbox, SinonSpy, createSandbox } from 'sinon'
import { baseUrl } from '../config'
import * as mailerService from '../services/mailerService'
import { authenticatedFetch } from './testSetup.spec'
import { authenticatedFetch, person } from './testSetup.spec'

describe('email verification via /verify-email?id=webId&token=base64Token', () => {
let sendMailSpy: SinonSpy<[options: Mail.Options], Promise<void>>
Expand Down Expand Up @@ -34,8 +34,8 @@ describe('email verification via /verify-email?id=webId&token=base64Token', () =
'@context': 'https://www.w3.org/ns/activitystreams',
'@id': '',
'@type': 'Add',
actor: 'http://localhost:3456/person/profile/card#me',
object: 'http://localhost:3456/person/profile/card',
actor: person.webId,
object: person.podUrl + 'profile/card',
target: '[email protected]',
}),
})
Expand Down
24 changes: 8 additions & 16 deletions src/test/integration-start.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Mail from 'nodemailer/lib/mailer'
import { SinonSandbox, SinonSpy, createSandbox } from 'sinon'
import { baseUrl } from '../config'
import * as mailerService from '../services/mailerService'
import { authenticatedFetch } from './testSetup.spec'
import { authenticatedFetch, person } from './testSetup.spec'

describe('Mailer integration via /inbox', () => {
let sendMailSpy: SinonSpy<[options: Mail.Options], Promise<void>>
Expand All @@ -30,8 +30,8 @@ describe('Mailer integration via /inbox', () => {
'@context': 'https://www.w3.org/ns/activitystreams',
'@id': '',
'@type': 'Add',
actor: 'http://localhost:3456/person/profile/card#me',
object: 'http://localhost:3456/person/inbox/',
actor: person.webId,
object: person.podUrl + 'inbox/',
target: '[email protected]',
}),
})
Expand All @@ -43,18 +43,10 @@ describe('Mailer integration via /inbox', () => {
)
expect(sendMailSpy.firstCall.firstArg)
.to.haveOwnProperty('text')
.include(
`verify-email?id=${encodeURIComponent(
'http://localhost:3456/person/profile/card#me',
)}&token=`,
)
.include(`verify-email?id=${encodeURIComponent(person.webId)}&token=`)
expect(sendMailSpy.firstCall.firstArg)
.to.haveOwnProperty('html')
.include(
`verify-email?id=${encodeURIComponent(
'http://localhost:3456/person/profile/card#me',
)}&token=`,
)
.include(`verify-email?id=${encodeURIComponent(person.webId)}&token=`)
expect(response.status).to.equal(200)
})

Expand All @@ -67,8 +59,8 @@ describe('Mailer integration via /inbox', () => {
'@context': 'https://www.w3.org/ns/activitystreams',
'@id': '',
'@type': 'Add',
actor: 'http://localhost:3456/person/profile/card#me',
object: 'http://localhost:3456/person/inbox/',
actor: person.webId,
object: person.podUrl + 'inbox/',
target: '[email protected]',
}),
})
Expand All @@ -87,7 +79,7 @@ describe('Mailer integration via /inbox', () => {
'@id': '',
'@type': 'Add',
actor: 'http://localhost:3456/person2/profile/card#me',
object: 'http://localhost:3456/person/inbox/',
object: person.podUrl + 'inbox/',
target: '[email protected]',
}),
})
Expand Down
18 changes: 11 additions & 7 deletions src/test/notification.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { baseUrl } from '../config'
import { getAuthenticatedFetch } from '../helpers'
import * as mailerService from '../services/mailerService'
import { addRead, setupInbox } from '../setup'
import { authenticatedFetch } from './testSetup.spec'
import { authenticatedFetch, person } from './testSetup.spec'

describe('received notification via /webhook-receiver', () => {
let sendMailSpy: SinonSpy<[options: Mail.Options], Promise<void>>
Expand All @@ -20,15 +20,19 @@ describe('received notification via /webhook-receiver', () => {
sendMailSpy = sandbox.spy(mailerService, 'sendMail')
})

afterEach(() => {
sandbox.restore()
})

beforeEach(async () => {
await setupInbox({
webId: 'http://localhost:3456/person/profile/card#me',
inbox: 'http://localhost:3456/person/inbox/',
webId: person.webId,
inbox: person.podUrl + 'inbox/',
authenticatedFetch,
})

await addRead({
resource: 'http://localhost:3456/person/inbox/',
resource: person.podUrl + 'inbox/',
agent: 'http://localhost:3456/bot/profile/card#me',
authenticatedFetch,
})
Expand All @@ -47,8 +51,8 @@ describe('received notification via /webhook-receiver', () => {
'@context': 'https://www.w3.org/ns/activitystreams',
'@id': '',
'@type': 'Add',
actor: 'http://localhost:3456/person/profile/card#me',
object: 'http://localhost:3456/person/inbox/',
actor: person.webId,
object: person.podUrl + 'inbox/',
target: '[email protected]',
}),
})
Expand All @@ -74,7 +78,7 @@ describe('received notification via /webhook-receiver', () => {
solidServer: 'http://localhost:3456',
})
const addToInboxResponse = await authenticatedPerson2Fetch(
'http://localhost:3456/person/inbox/',
person.podUrl + 'inbox/',
{
method: 'POST',
headers: {
Expand Down
110 changes: 110 additions & 0 deletions src/test/status.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { expect } from 'chai'
import * as cheerio from 'cheerio'
import { describe } from 'mocha'
import Mail from 'nodemailer/lib/mailer'
import { SinonSandbox, SinonSpy, createSandbox } from 'sinon'
import { baseUrl } from '../config'
import * as mailerService from '../services/mailerService'
import { addRead, setupInbox } from '../setup'
import { authenticatedFetch, person } from './testSetup.spec'

describe('get info about integrations of current person with GET /status', () => {
let sendMailSpy: SinonSpy<[options: Mail.Options], Promise<void>>
let verificationLink: string
let sandbox: SinonSandbox

beforeEach(() => {
sandbox = createSandbox()
sendMailSpy = sandbox.spy(mailerService, 'sendMail')
})

afterEach(() => {
sandbox.restore()
})

beforeEach(async () => {
await setupInbox({
webId: person.webId,
inbox: `${person.podUrl}inbox/`,
authenticatedFetch,
})

await addRead({
resource: `${person.podUrl}inbox/`,
agent: 'http://localhost:3456/bot/profile/card#me',
authenticatedFetch,
})
})

beforeEach(async function () {
this.timeout(10000)
// initialize the integration
const initResponse = await authenticatedFetch(`${baseUrl}/inbox`, {
method: 'post',
headers: {
'content-type':
'application/ld+json;profile="https://www.w3.org/ns/activitystreams"',
},
body: JSON.stringify({
'@context': 'https://www.w3.org/ns/activitystreams',
'@id': '',
'@type': 'Add',
actor: person.webId,
object: `${person.podUrl}inbox/`,
target: '[email protected]',
}),
})

expect(initResponse.status).to.equal(200)
// email was sent
const emailMessage = sendMailSpy.firstCall.firstArg.html
const $ = cheerio.load(emailMessage)
verificationLink = $('a').first().attr('href') as string
expect(verificationLink).to.not.be.null
})

it('[not authenticated] should fail with 401', async () => {
const response = await fetch(`${baseUrl}/status`)
expect(response.status).to.equal(401)
})

it('should show list of resources (inboxes) current user is observing', async () => {
// finish the integration
const finishResponse = await fetch(verificationLink)
expect(finishResponse.status).to.equal(200)

const response = await authenticatedFetch(`${baseUrl}/status`)
expect(response.status).to.equal(200)

const body = await response.json()

expect(body).to.deep.equal({
actor: person.webId,
integrations: [
{
object: `${person.podUrl}inbox/`,
target: '[email protected]',
verified: true,
},
],
})
})

it('should show unverified integrations', async () => {
const response = await authenticatedFetch(`${baseUrl}/status`)
expect(response.status).to.equal(200)

const body = await response.json()

expect(body).to.deep.equal({
actor: person.webId,
integrations: [
{
object: `${person.podUrl}inbox/`,
target: '[email protected]',
verified: false,
},
],
})
})
})
Loading

0 comments on commit 89d2210

Please sign in to comment.