Skip to content

Commit e742b34

Browse files
committed
Fix plus-sign recovery email handling
1 parent 2cfa32c commit e742b34

4 files changed

Lines changed: 87 additions & 39 deletions

File tree

lib/models/account-manager.mjs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -231,14 +231,14 @@ class AccountManager {
231231
return this.store.getGraph(rootAclUri)
232232
})
233233
.then(rootAclGraph => {
234-
const matches = rootAclGraph.match(null, ns.acl('agent'))
235-
let recoveryMailto = matches.find(agent => agent.object.value.startsWith('mailto:'))
236-
if (recoveryMailto) {
237-
recoveryMailto = recoveryMailto.object.value.replace('mailto:', '')
238-
}
239-
return recoveryMailto
240-
})
241-
}
234+
const matches = rootAclGraph.match(null, ns.acl('agent'))
235+
let recoveryMailto = matches.find(agent => agent.object.value.startsWith('mailto:'))
236+
if (recoveryMailto) {
237+
recoveryMailto = decodeURIComponent(recoveryMailto.object.value.replace('mailto:', ''))
238+
}
239+
return recoveryMailto
240+
})
241+
}
242242

243243
verifyEmailDependencies (userAccount) {
244244
if (!this.emailService) {

lib/models/account-template.mjs

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { URL } from 'url'
99
export const TEMPLATE_EXTENSIONS = ['.acl', '.meta', '.json', '.hbs', '.handlebars']
1010
export const TEMPLATE_FILES = ['card']
1111

12-
class AccountTemplate {
12+
class AccountTemplate {
1313
constructor (options = {}) {
1414
this.substitutions = options.substitutions || {}
1515
this.templateExtensions = options.templateExtensions || TEMPLATE_EXTENSIONS
@@ -26,17 +26,28 @@ class AccountTemplate {
2626
return fsUtils.copyTemplateDir(templatePath, accountPath)
2727
}
2828

29-
static templateSubstitutionsFor (userAccount) {
30-
const webUri = new URL(userAccount.webId)
31-
const podRelWebId = userAccount.webId.replace(webUri.origin, '')
32-
const substitutions = {
33-
name: userAccount.displayName,
34-
webId: userAccount.externalWebId ? userAccount.webId : podRelWebId,
35-
email: userAccount.email,
36-
idp: userAccount.idp
37-
}
38-
return substitutions
39-
}
29+
static templateSubstitutionsFor (userAccount) {
30+
const webUri = new URL(userAccount.webId)
31+
const podRelWebId = userAccount.webId.replace(webUri.origin, '')
32+
const substitutions = {
33+
name: userAccount.displayName,
34+
webId: userAccount.externalWebId ? userAccount.webId : podRelWebId,
35+
email: AccountTemplate.encodeMailtoAddress(userAccount.email),
36+
idp: userAccount.idp
37+
}
38+
return substitutions
39+
}
40+
41+
static encodeMailtoAddress (email) {
42+
if (!email) {
43+
return email
44+
}
45+
const [localPart, domain] = email.split('@')
46+
if (!domain) {
47+
return encodeURIComponent(email)
48+
}
49+
return `${encodeURIComponent(localPart)}@${domain}`
50+
}
4051

4152
readAccountFiles (accountPath) {
4253
return new Promise((resolve, reject) => {

test/unit/account-manager-test.mjs

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -358,8 +358,8 @@ describe('AccountManager', () => {
358358
})
359359

360360
describe('loadAccountRecoveryEmail()', () => {
361-
it('parses and returns the agent mailto from the root acl', () => {
362-
const userAccount = UserAccount.from({ username: 'alice' })
361+
it('parses and returns the agent mailto from the root acl', () => {
362+
const userAccount = UserAccount.from({ username: 'alice' })
363363

364364
const rootAclGraph = rdf.graph()
365365
rootAclGraph.add(
@@ -377,13 +377,37 @@ describe('AccountManager', () => {
377377
const accountManager = AccountManager.from(options)
378378

379379
return accountManager.loadAccountRecoveryEmail(userAccount)
380-
.then(recoveryEmail => {
381-
expect(recoveryEmail).to.equal('alice@example.com')
382-
})
383-
})
384-
385-
it('should return undefined when agent mailto is missing', () => {
386-
const userAccount = UserAccount.from({ username: 'alice' })
380+
.then(recoveryEmail => {
381+
expect(recoveryEmail).to.equal('alice@example.com')
382+
})
383+
})
384+
385+
it('decodes encoded plus signs in the agent mailto from the root acl', () => {
386+
const userAccount = UserAccount.from({ username: 'solidgold' })
387+
388+
const rootAclGraph = rdf.graph()
389+
rootAclGraph.add(
390+
rdf.namedNode('https://solidgold.example.com/.acl#owner'),
391+
ns.acl('agent'),
392+
rdf.namedNode('mailto:jon%2Bsolidtest@snap.com')
393+
)
394+
395+
const store = {
396+
suffixAcl: '.acl',
397+
getGraph: sinon.stub().resolves(rootAclGraph)
398+
}
399+
400+
const options = { host, multiuser: true, store }
401+
const accountManager = AccountManager.from(options)
402+
403+
return accountManager.loadAccountRecoveryEmail(userAccount)
404+
.then(recoveryEmail => {
405+
expect(recoveryEmail).to.equal('jon+solidtest@snap.com')
406+
})
407+
})
408+
409+
it('should return undefined when agent mailto is missing', () => {
410+
const userAccount = UserAccount.from({ username: 'alice' })
387411

388412
const emptyGraph = rdf.graph()
389413

test/unit/account-template-test.mjs

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,19 +40,32 @@ describe('AccountTemplate', () => {
4040
})
4141

4242
describe('templateSubstitutionsFor()', () => {
43-
it('should init', () => {
44-
const userOptions = {
45-
username: 'alice',
46-
webId: 'https://alice.example.com/profile/card#me',
47-
name: 'Alice Q.',
43+
it('should init', () => {
44+
const userOptions = {
45+
username: 'alice',
46+
webId: 'https://alice.example.com/profile/card#me',
47+
name: 'Alice Q.',
4848
email: 'alice@example.com'
4949
}
5050
const userAccount = UserAccount.from(userOptions)
5151

5252
const substitutions = AccountTemplate.templateSubstitutionsFor(userAccount)
5353
expect(substitutions.name).to.equal('Alice Q.')
54-
expect(substitutions.email).to.equal('alice@example.com')
55-
expect(substitutions.webId).to.equal('/profile/card#me')
56-
})
57-
})
58-
})
54+
expect(substitutions.email).to.equal('alice@example.com')
55+
expect(substitutions.webId).to.equal('/profile/card#me')
56+
})
57+
58+
it('encodes plus signs in email addresses for mailto templates', () => {
59+
const userOptions = {
60+
username: 'solidgold',
61+
webId: 'https://solidgold.example.com/profile/card#me',
62+
name: 'Solid Gold',
63+
email: 'jon+solidtest@snap.com'
64+
}
65+
const userAccount = UserAccount.from(userOptions)
66+
67+
const substitutions = AccountTemplate.templateSubstitutionsFor(userAccount)
68+
expect(substitutions.email).to.equal('jon%2Bsolidtest@snap.com')
69+
})
70+
})
71+
})

0 commit comments

Comments
 (0)