Skip to content

Commit

Permalink
Handle email verification for pods without notification support (#8)
Browse files Browse the repository at this point in the history
When notification subscription fails, we verify the email, but we inform user that email notifications won't arrive
  • Loading branch information
mrkvon authored Oct 13, 2023
1 parent fd4a9d7 commit 2b00db4
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 9 deletions.
20 changes: 13 additions & 7 deletions src/controllers/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export const initializeIntegration: Middleware = async ctx => {
await sendMail({
from: config.emailSender,
to: email,
subject: 'TODO',
subject: 'Verify your email for sleepy.bike notifications',
html: `Please verify your email <a href="${emailVerificationLink}">click here</a>`,
text: `Please verify your email ${emailVerificationLink}`,
})
Expand Down Expand Up @@ -103,12 +103,18 @@ export const finishIntegration: Middleware = async ctx => {
// save the integration to database
await Integration.create(integrationData)

// subscribe to the inbox' webhook notifications
await subscribeForNotifications(integrationData.inbox)

ctx.response.body =
'Email notifications have been successfully integrated to your inbox'
ctx.response.status = 200
try {
// subscribe to the inbox' webhook notifications
await subscribeForNotifications(integrationData.inbox)
ctx.response.body =
'Email notifications have been successfully integrated to your inbox'
} catch (e) {
ctx.response.body =
"Email was successfully verified, but notifications won't work, yet. It looks like your Solid Pod doesn't support notifications. We'll implement a workaround in the future.\nError: " +
(e as Error).message
} finally {
ctx.response.status = 200
}
}

type Fetch = typeof fetch
Expand Down
41 changes: 41 additions & 0 deletions src/test/css-config-no-notifications.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^6.0.0/components/context.jsonld",
"import": [
"css:config/app/main/default.json",
"css:config/app/init/initialize-prefilled-root.json",
"css:config/app/setup/optional.json",
"css:config/app/variables/default.json",
"css:config/http/handler/default.json",
"css:config/http/middleware/default.json",
"css:config/http/notifications/disabled.json",
"css:config/http/server-factory/http.json",
"css:config/http/static/default.json",
"css:config/identity/access/public.json",
"css:config/identity/email/default.json",
"css:config/identity/handler/default.json",
"css:config/identity/ownership/token.json",
"css:config/identity/pod/static.json",
"css:config/identity/registration/enabled.json",
"css:config/ldp/authentication/dpop-bearer.json",
"css:config/ldp/authorization/webacl.json",
"css:config/ldp/handler/default.json",
"css:config/ldp/metadata-parser/default.json",
"css:config/ldp/metadata-writer/default.json",
"css:config/ldp/modes/default.json",
"css:config/storage/backend/memory.json",
"css:config/storage/key-value/resource-store.json",
"css:config/storage/middleware/default.json",
"css:config/util/auxiliary/acl.json",
"css:config/util/identifiers/suffix.json",
"css:config/util/index/default.json",
"css:config/util/logging/winston.json",
"css:config/util/representation-conversion/default.json",
"css:config/util/resource-locker/memory.json",
"css:config/util/variables/default.json"
],
"@graph": [
{
"comment": "A Solid server that stores its resources in memory and uses WAC for authorization."
}
]
}
42 changes: 41 additions & 1 deletion src/test/integration-finish.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import Mail from 'nodemailer/lib/mailer'
import { SinonSandbox, SinonSpy, createSandbox } from 'sinon'
import { baseUrl } from '../config'
import * as mailerService from '../services/mailerService'
import { authenticatedFetch, person } from './testSetup.spec'
import {
authenticatedFetch,
authenticatedFetchNoNotifications,
person,
personNoNotifications,
} 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 @@ -85,4 +90,39 @@ describe('email verification via /verify-email?id=webId&token=base64Token', () =
})

it('when we send out multiple verification emails, the last link should work')

context("server doesn't support webhook notifications", () => {
beforeEach(async () => {
// initialize the integration
const initResponse = await authenticatedFetchNoNotifications(
`${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: personNoNotifications.webId,
object: personNoNotifications.podUrl + 'profile/card',
target: '[email protected]',
}),
},
)

expect(initResponse.status).to.equal(200)
// email was sent
const emailMessage = sendMailSpy.secondCall.firstArg.html
const $ = cheerio.load(emailMessage)
verificationLink = $('a').first().attr('href') as string
expect(verificationLink).to.not.be.null
})
it("should verify email, but inform user that notifications aren't supported", async () => {
const response = await fetch(verificationLink)
expect(response.status).to.equal(200)
})
})
})
64 changes: 63 additions & 1 deletion src/test/testSetup.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { createRandomAccount, getAuthenticatedFetch } from '../helpers'

let server: Server<typeof IncomingMessage, typeof ServerResponse>
let authenticatedFetch: typeof fetch
let authenticatedFetchNoNotifications: typeof fetch
let person: {
idp: string
podUrl: string
Expand All @@ -16,7 +17,16 @@ let person: {
password: string
email: string
}
let personNoNotifications: {
idp: string
podUrl: string
webId: string
username: string
password: string
email: string
}
let cssServer: css.App
let cssServerNoNotifications: css.App

before(async function () {
this.timeout(60000)
Expand Down Expand Up @@ -51,6 +61,39 @@ after(async () => {
await cssServer.stop()
})

before(async function () {
this.timeout(60000)
const start = Date.now()

// eslint-disable-next-line no-console
console.log('Starting CSS server without notifications')
// Community Solid Server (CSS) set up following example in https://github.com/CommunitySolidServer/hello-world-component/blob/main/test/integration/Server.test.ts
cssServerNoNotifications = await new css.AppRunner().create(
{
mainModulePath: css.joinFilePath(__dirname, '../../'), // ?
typeChecking: false, // ?
dumpErrorState: false, // disable CSS error dump
},
css.joinFilePath(__dirname, './css-config-no-notifications.json'), // CSS config
{},
// CSS cli options
// https://github.com/CommunitySolidServer/CommunitySolidServer/tree/main#-parameters
{
port: 3457,
loggingLevel: 'off',
// seededPodConfigJson: css.joinFilePath(__dirname, './css-pod-seed.json'), // set up some Solid accounts
},
)
await cssServerNoNotifications.start()

// eslint-disable-next-line no-console
console.log('CSS server started in', (Date.now() - start) / 1000, 'seconds')
})

after(async () => {
await cssServerNoNotifications.stop()
})

before(done => {
server = app.listen(port, done)
})
Expand Down Expand Up @@ -89,4 +132,23 @@ beforeEach(async () => {
})
})

export { authenticatedFetch, cssServer, person, server }
beforeEach(async () => {
personNoNotifications = await createRandomAccount({
solidServer: 'http://localhost:3457',
})
authenticatedFetchNoNotifications = await getAuthenticatedFetch({
email: personNoNotifications.email,
password: personNoNotifications.password,
solidServer: 'http://localhost:3457',
})
})

export {
authenticatedFetch,
authenticatedFetchNoNotifications,
cssServer,
cssServerNoNotifications,
person,
personNoNotifications,
server,
}

0 comments on commit 2b00db4

Please sign in to comment.