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

Handle email verification for pods without notification support #8

Merged
merged 1 commit into from
Oct 13, 2023
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
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,
}