Skip to content

Commit

Permalink
feat : added msisdn in the suppported media for store-invite
Browse files Browse the repository at this point in the history
  • Loading branch information
h1ppox99 committed Jul 12, 2024
1 parent ed0f03d commit b954aa6
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 55 deletions.
104 changes: 75 additions & 29 deletions packages/matrix-identity-server/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -705,7 +705,7 @@ describe('Use configuration file', () => {

describe('/_matrix/identity/v2/3pid/bind', () => {
it('should find the 3pid - matrixID association after binding', async () => {
const response_request_token = await request(app)
const responseRequestToken = await request(app)
.post('/_matrix/identity/v2/validate/email/requestToken')
.set('Authorization', `Bearer ${validToken}`)
.set('Accept', 'application/json')
Expand All @@ -715,22 +715,22 @@ describe('Use configuration file', () => {
next_link: 'http://localhost:8090',
send_attempt: 1
})
expect(response_request_token.statusCode).toBe(200)
expect(responseRequestToken.statusCode).toBe(200)
expect(sendMailMock.mock.calls[0][0].to).toBe('ab@abc.fr')
expect(sendMailMock.mock.calls[0][0].raw).toMatch(
/token=([a-zA-Z0-9]{64})&client_secret=mysecret2&sid=([a-zA-Z0-9]{64})/
)
const bind_token = RegExp.$1
const bind_sid = RegExp.$2
const response_submit_token = await request(app)
const bindToken = RegExp.$1
const bindSid = RegExp.$2
const responseSubmitToken = await request(app)
.post('/_matrix/identity/v2/validate/email/submitToken')
.send({
token: bind_token,
token: bindToken,
client_secret: 'mysecret2',
sid: bind_sid
sid: bindSid
})
.set('Accept', 'application/json')
expect(response_submit_token.statusCode).toBe(200)
expect(responseSubmitToken.statusCode).toBe(200)
const longKeyPair: {
publicKey: string
privateKey: string
Expand All @@ -741,17 +741,17 @@ describe('Use configuration file', () => {
public: longKeyPair.publicKey,
private: longKeyPair.privateKey
})
const response_bind = await request(app)
const responseBind = await request(app)
.post('/_matrix/identity/v2/3pid/bind')
.set('Authorization', `Bearer ${validToken}`)
.set('Accept', 'application/json')
.send({
client_secret: 'mysecret2',
sid: bind_sid,
sid: bindSid,
mxid: '@ab:abc.fr'
})
expect(response_bind.statusCode).toBe(200)
expect(response_bind.body).toHaveProperty('signatures')
expect(responseBind.statusCode).toBe(200)
expect(responseBind.body).toHaveProperty('signatures')
await idServer.cronTasks?.ready
const response = await request(app)
.get('/_matrix/identity/v2/hash_details')
Expand All @@ -763,7 +763,7 @@ describe('Use configuration file', () => {
const hash = new Hash()
await hash.ready
const computedHash = hash.sha256(`ab@abc.fr mail ${pepper}`)
const response_lookup = await request(app)
const responseLookup = await request(app)
.post('/_matrix/identity/v2/lookup')
.send({
addresses: [computedHash],
Expand All @@ -772,8 +772,8 @@ describe('Use configuration file', () => {
})
.set('Authorization', `Bearer ${validToken}`)
.set('Accept', 'application/json')
expect(response_lookup.statusCode).toBe(200)
expect(response_lookup.body.mappings).toEqual({
expect(responseLookup.statusCode).toBe(200)
expect(responseLookup.body.mappings).toEqual({
[computedHash]: '@ab:abc.fr'
})
})
Expand Down Expand Up @@ -908,7 +908,7 @@ describe('Use configuration file', () => {
expect(response.statusCode).toBe(400)
})
it('should refuse incompatible session_id and client_secret', async () => {
const response_request_token = await request(app)
const responseRequestToken = await request(app)
.post('/_matrix/identity/v2/validate/email/requestToken')
.set('Authorization', `Bearer ${validToken}`)
.set('Accept', 'application/json')
Expand All @@ -917,24 +917,24 @@ describe('Use configuration file', () => {
email: 'unbind@unbind.fr',
send_attempt: 1
})
expect(response_request_token.statusCode).toBe(200)
expect(responseRequestToken.statusCode).toBe(200)
expect(sendMailMock).toHaveBeenCalled()
expect(sendMailMock.mock.calls[0][0].to).toBe('unbind@unbind.fr')
expect(sendMailMock.mock.calls[0][0].raw).toMatch(
/token=([a-zA-Z0-9]{64})&client_secret=mysecret4&sid=([a-zA-Z0-9]{64})/
)
token4 = RegExp.$1
sid4 = response_request_token.body.sid
const response_submit_token = await request(app)
sid4 = responseRequestToken.body.sid
const responseSubmitToken = await request(app)
.post('/_matrix/identity/v2/validate/email/submitToken')
.send({
token: token4,
client_secret: 'mysecret4',
sid: sid4
})
.set('Accept', 'application/json')
expect(response_submit_token.statusCode).toBe(200)
const response_bind = await request(app)
expect(responseSubmitToken.statusCode).toBe(200)
const responseBind = await request(app)
.post('/_matrix/identity/v2/3pid/bind')
.set('Authorization', `Bearer ${validToken}`)
.set('Accept', 'application/json')
Expand All @@ -943,7 +943,7 @@ describe('Use configuration file', () => {
sid: sid4,
mxid: '@unbind:unbind.fr'
})
expect(response_bind.statusCode).toBe(200)
expect(responseBind.statusCode).toBe(200)
const response = await request(app)
.post('/_matrix/identity/v2/3pid/unbind')
.set('Authorization', `Bearer ${validToken}`)
Expand Down Expand Up @@ -1159,7 +1159,21 @@ describe('Use configuration file', () => {
sender: '@dwho:matrix.org'
})
expect(response.statusCode).toBe(400)
expect(response.body.errcode).toEqual('M_INVALID_EMAIL')
expect(response.body.errcode).toEqual('M_INVALID_PARAM')
})
it('should reject an invalid phone number', async () => {
const response = await request(app)
.post('/_matrix/identity/v2/store-invite')
.set('Authorization', `Bearer ${validToken}`)
.set('Accept', 'application/json')
.send({
address: '+1234',
medium: 'msisdn',
room_id: '!room:matrix.org',
sender: '@dwho:matrix.org'
})
expect(response.statusCode).toBe(400)
expect(response.body.errcode).toEqual('M_INVALID_PARAM')
})
it('should alert if the lookup API did not behave as expected', async () => {
const mockResponse = Promise.resolve({
Expand Down Expand Up @@ -1217,7 +1231,7 @@ describe('Use configuration file', () => {
expect(response.statusCode).toBe(400)
expect(response.body.errcode).toBe('M_THREEPID_IN_USE')
})
it('should accept a valid request', async () => {
it('should accept a valid email request', async () => {
const mockResponse = Promise.resolve({
ok: false,
status: 400,
Expand Down Expand Up @@ -1250,6 +1264,38 @@ describe('Use configuration file', () => {
expect(response.body).toHaveProperty('token')
expect(response.body.token).toMatch(/^[a-zA-Z0-9]{64}$/)
})
it('should accept a valid phone number request', async () => {
const mockResponse = Promise.resolve({
ok: false,
status: 400,
json: () => {
return {
errcode: 'M_INVALID_PEPPER',
error: 'Unknown or invalid pepper - has it been rotated?'
}
}
})
// @ts-expect-error mock is unknown
fetch.mockImplementation(async () => await mockResponse)
await mockResponse
const response = await request(app)
.post('/_matrix/identity/v2/store-invite')
.set('Authorization', `Bearer ${validToken}`)
.set('Accept', 'application/json')
.send({
address: '+33612345678',
medium: 'msisdn',
room_id: '!room:matrix.org',
sender: '@dwho:matrix.org'
})
expect(response.statusCode).toBe(200)
// TODO : add call to smsMock when it will be implemented
expect(response.body).toHaveProperty('display_name')
expect(response.body.display_name).not.toBe('+33612345678')
expect(response.body).toHaveProperty('public_keys')
expect(response.body).toHaveProperty('token')
expect(response.body.token).toMatch(/^[a-zA-Z0-9]{64}$/)
})
})

describe('/_matrix/identity/v2/sign-ed25519 ', () => {
Expand Down Expand Up @@ -1292,7 +1338,7 @@ describe('Use configuration file', () => {
// @ts-expect-error mock is unknown
fetch.mockImplementation(async () => await mockResponse)
await mockResponse
const response_store_invit = await request(app)
const responseStoreInvite = await request(app)
.post('/_matrix/identity/v2/store-invite')
.set('Authorization', `Bearer ${validToken}`)
.set('Accept', 'application/json')
Expand All @@ -1302,16 +1348,16 @@ describe('Use configuration file', () => {
room_id: '!room:matrix.org',
sender: '@dwho:matrix.org'
})
expect(response_store_invit.statusCode).toBe(200)
token = response_store_invit.body.token
expect(responseStoreInvite.statusCode).toBe(200)
token = responseStoreInvite.body.token
const response = await request(app)
.post('/_matrix/identity/v2/sign-ed25519')
.set('Authorization', `Bearer ${validToken}`)
.set('Accept', 'application/json')
.send({
mxid: 'invalid_mxid',
private_key: keyPair.privateKey,
token: token
token
})
expect(response.statusCode).toBe(400)
})
Expand Down Expand Up @@ -1347,7 +1393,7 @@ describe('Use configuration file', () => {
.send({
mxid: '@test:matrix.org',
private_key: keyPair.privateKey,
token: token
token
})
expect(response.statusCode).toBe(200)
expect(response.body).toHaveProperty('signatures')
Expand Down
84 changes: 58 additions & 26 deletions packages/matrix-identity-server/src/invitation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ const preConfigureTemplate = (
)
}

// TODO : don't forget to modify this : cf matrix.to or other method
// TODO : modify this if necessary

Check notice

Code scanning / devskim

A "TODO" or similar was left in source code, possibly indicating incomplete functionality Note

Suspicious comment
const inviteLink = (
server: string,
senderId: string,
Expand Down Expand Up @@ -102,39 +102,63 @@ const mailBody = (
}

// To complete if another 3PID is added for this endpoint
const validMediums: string[] = ['email']
const validMediums: string[] = ['email', 'msisdn']

// Regular expressions for different mediums
const validEmailRe = /^\w[+.-\w]*\w@\w[.-\w]*\w\.\w{2,6}$/
const validPhoneRe = /^(\+[1-9]\d{0,2})?\d{4,14}$/

const redactAddress = (address: string): string => {
// Assuming that the address is a valid email address
const atIndex = address.indexOf('@')
const localPart = address.slice(0, atIndex)
const domainPart = address.slice(atIndex + 1)
const redactAddress = (medium: string, address: string): string => {
switch (medium) {
case 'email': {
const atIndex = address.indexOf('@')
const localPart = address.slice(0, atIndex)
const domainPart = address.slice(atIndex + 1)

const replaceRandomCharacters = (
str: string,
redactionRatio: number
): string => {
const chars = str.split('')
const redactionCount = Math.ceil(chars.length * redactionRatio)
const redactedLocalPart = replaceLastCharacters(localPart)
const redactedDomainPart = replaceLastCharacters(domainPart)

for (let i = 0; i < redactionCount; i++) {
const index = i * Math.floor(chars.length / redactionCount)
chars[index] = '*'
return `${redactedLocalPart}@${redactedDomainPart}`
}

return chars.join('')
case 'msisdn':
return replaceLastCharacters(address)
/* istanbul ignore next : call to redactAddress is done after checking if the medium was valid */
default:
return address
}
}

const replaceLastCharacters = (
str: string,
redactionRatio: number = 0.4
): string => {
const chars = str.split('')
const redactionCount = Math.ceil(chars.length * redactionRatio)

const redactionRatio = 0.3 // Redact 30% of the characters
const redactedLocalPart = replaceRandomCharacters(localPart, redactionRatio)
const redactedDomainPart = replaceRandomCharacters(domainPart, redactionRatio)
// Replace the last `redactionCount` characters with '*'
for (let i = chars.length - redactionCount; i < chars.length; i++) {
chars[i] = '*'
}

return `${redactedLocalPart}@${redactedDomainPart}`
return chars.join('')
}

// const redactEmailAddress = (address: string): string => {
// // Assuming that the address is a valid email address
// const atIndex = address.indexOf('@')
// const localPart = address.slice(0, atIndex)
// const domainPart = address.slice(atIndex + 1)

// const redactedLocalPart = replaceLastCharacters(localPart)
// const redactedDomainPart = replaceLastCharacters(domainPart)

// return `${redactedLocalPart}@${redactedDomainPart}`
// }

// const redactPhoneNumber = (phoneNumber: string): string => {
// return replaceLastCharacters(phoneNumber)
// }

const StoreInvit = <T extends string = never>(
idServer: MatrixIdentityServer<T>
): expressAppHandler => {
Expand Down Expand Up @@ -165,10 +189,16 @@ const StoreInvit = <T extends string = never>(
switch (_medium) {
case 'email':
if (!validEmailRe.test(_address)) {
send(res, 400, errMsg('invalidEmail'))
send(res, 400, errMsg('invalidParam', 'Invalid email address.'))
return
}
// TODO : add phone number validation
break
case 'msisdn':
if (!validPhoneRe.test(_address)) {
send(res, 400, errMsg('invalidParam', 'Invalid phone number.'))
return
}
break
}
// Call to the lookup API to check for any existing third-party identifiers
try {
Expand Down Expand Up @@ -232,10 +262,12 @@ const StoreInvit = <T extends string = never>(
)
})
break
// TODO : add sms sending
case 'msisdn':
// TO DO implement smsSender
break
}
// Send 200 response
const redactedAddress = redactAddress(_address)
const redactedAddress = redactAddress(_medium, _address)
idServer.db
.getKeys('current')
.then((keys) => {
Expand Down

0 comments on commit b954aa6

Please sign in to comment.