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

🐛 Store invite fix #169

Merged
merged 17 commits into from
Jan 27, 2025
Merged
Show file tree
Hide file tree
Changes from 9 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
2 changes: 1 addition & 1 deletion packages/crypto/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import _nacl, { type Nacl } from 'js-nacl'
import nacl from 'tweetnacl'
import * as naclUtil from 'tweetnacl-util'
import naclUtil from 'tweetnacl-util'

// export const supportedHashes = ['sha256', 'sha512']
export const supportedHashes = ['sha256']
Expand Down
4 changes: 1 addition & 3 deletions packages/matrix-identity-server/src/db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -686,9 +686,7 @@ class IdentityServerDb<T extends string = never>
return new Promise((resolve, reject) => {
const _type = type === 'current' ? 'currentKey' : 'previousKey'
this.db
.get('longTermKeypairs', ['keyID', 'public', 'private'], {
name: _type
})
.getAll('shortTermKeypairs', ['keyID', 'public', 'private'])
.then((rows) => {
if (rows.length === 0) {
reject(new Error(`No ${_type} found`))
Expand Down
39 changes: 30 additions & 9 deletions packages/matrix-identity-server/src/invitation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import type MatrixIdentityServer from '../index'
import { type Config } from '../types'
import {
errMsg,
jsonContent,
send,
validateParameters,
type expressAppHandler
} from '@twake/utils'
import Mailer from '../utils/mailer'
import validator from 'validator'
import { buildUrl } from '../utils'

interface storeInvitationArgs {
address: string
Expand Down Expand Up @@ -163,11 +164,12 @@ const StoreInvit = <T extends string = never>(
)
return (req, res) => {
idServer.authenticate(req, res, (_data, _id) => {
jsonContent(req, res, idServer.logger, (obj) => {
// eslint-disable-next-line @typescript-eslint/no-misused-promises
validateParameters(res, schema, obj, idServer.logger, async (obj) => {
const obj = (req as any).body
validateParameters(res, schema, obj, idServer.logger, async (obj) => {
try {
const medium = (obj as storeInvitationArgs).medium
if (!validMediums.includes(medium)) {
console.error('invalid medium')
send(
res,
400,
Expand All @@ -182,12 +184,14 @@ const StoreInvit = <T extends string = never>(
switch (medium) {
case 'email':
if (address == null || !validEmailRe.test(address)) {
console.error('invalid email address')
send(res, 400, errMsg('invalidParam', 'Invalid email address.'))
return
} else mediumAddress = address
break
case 'msisdn':
if (phone == null || !validPhoneRe.test(phone)) {
if (phone == null || !validator.isMobilePhone(phone)) {
console.error('invalid phone number')
send(res, 400, errMsg('invalidParam', 'Invalid phone number.'))
return
} else mediumAddress = phone
Expand All @@ -200,8 +204,9 @@ const StoreInvit = <T extends string = never>(
const _pepper = idServer.db.get('keys', ['data'], {
name: 'pepper'
})

const response = await fetch(
`https://${idServer.conf.server_name}/_matrix/identity/v2/lookup`,
buildUrl(idServer.conf.base_url, '/_matrix/identity/v2/lookup'),
{
method: 'POST',
headers: {
Expand All @@ -216,14 +221,20 @@ const StoreInvit = <T extends string = never>(
})
}
)
if (response.status === 200) {

const result = (await response.json()) as {
mappings: Record<string, string>
}
const foundMappings = Object.keys(result.mappings).length > 0

if (response.status === 200 && foundMappings) {
send(res, 400, {
errcode: 'M_THREEPID_IN_USE',
error:
'The third party identifier is already in use by another user.',
mxid: (obj as storeInvitationArgs).sender
})
} else if (response.status === 400) {
} else if (response.status === 200 && !foundMappings) {
// Create invitation token
const ephemeralKey = await idServer.db.createKeypair(
'shortTerm',
Expand Down Expand Up @@ -282,6 +293,7 @@ const StoreInvit = <T extends string = never>(
send(res, 200, responseBody)
})
.catch((err) => {
console.error('error while getting keys', { err })
/* istanbul ignore next */
idServer.logger.debug(
'Error while getting the current key',
Expand All @@ -291,6 +303,10 @@ const StoreInvit = <T extends string = never>(
send(res, 500, errMsg('unknown', err))
})
} else {
console.error('unexpected response status', {
status: response.status,
result
})
/* istanbul ignore next */
idServer.logger.error(
'Unexpected response statusCode from the /_matrix/identity/v2/lookup API'
Expand All @@ -305,6 +321,9 @@ const StoreInvit = <T extends string = never>(
)
}
} catch (err) {
console.error('error while making a call to the lookup API', {
err
})
/* istanbul ignore next */
idServer.logger.error(
'Error while making a call to the lookup API (/_matrix/identity/v2/lookup)',
Expand All @@ -313,7 +332,9 @@ const StoreInvit = <T extends string = never>(
/* istanbul ignore next */
send(res, 500, errMsg('unknown', err as string))
}
})
} catch (error) {
console.error({ error })
}
})
})
}
Expand Down
87 changes: 43 additions & 44 deletions packages/matrix-identity-server/src/lookup/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,53 +18,52 @@ const lookup = <T extends string = never>(
): expressAppHandler => {
return (req, res) => {
idServer.authenticate(req, res, (data, id) => {
jsonContent(req, res, idServer.logger, (obj) => {
validateParameters(res, schema, obj, idServer.logger, (obj) => {
if (
!(
Array.isArray((obj as { addresses: string[] }).addresses) &&
(obj as { addresses: string[] }).addresses.every(
(address) => typeof address === 'string'
) &&
(obj as { addresses: string[] }).addresses.length <=
(idServer.conf.hashes_rate_limit as number)
)
) {
/* istanbul ignore next */
send(res, 400, errMsg('invalidParam'))
} else {
idServer.logger.debug(
`lookup request to search ${JSON.stringify(obj)}`
)
idServer.db
.get('hashes', ['value', 'hash', 'active'], {
hash: (obj as { addresses: string[] }).addresses
})
.then((rows) => {
// send(res, 200, rows)
const mappings: Record<string, string> = {}
const inactives: Record<string, string> = {}
rows.forEach((row) => {
if (row.active === 1) {
// @ts-expect-error row.hash is not null
mappings[row.hash] = row.value
} else {
// @ts-expect-error row.hash is not null
inactives[row.hash] = row.value
}
})
if (idServer.conf.additional_features ?? false) {
send(res, 200, { mappings, inactive_mappings: inactives })
const obj = (req as any).body
validateParameters(res, schema, obj, idServer.logger, (obj) => {
if (
!(
Array.isArray((obj as { addresses: string[] }).addresses) &&
(obj as { addresses: string[] }).addresses.every(
(address) => typeof address === 'string'
) &&
(obj as { addresses: string[] }).addresses.length <=
(idServer.conf.hashes_rate_limit as number)
)
) {
/* istanbul ignore next */
send(res, 400, errMsg('invalidParam'))
} else {
idServer.logger.debug(
`lookup request to search ${JSON.stringify(obj)}`
)
idServer.db
.get('hashes', ['value', 'hash', 'active'], {
hash: (obj as { addresses: string[] }).addresses
})
.then((rows) => {
// send(res, 200, rows)
const mappings: Record<string, string> = {}
const inactives: Record<string, string> = {}
rows.forEach((row) => {
if (row.active === 1) {
// @ts-expect-error row.hash is not null
mappings[row.hash] = row.value
} else {
send(res, 200, { mappings })
// @ts-expect-error row.hash is not null
inactives[row.hash] = row.value
}
})
.catch((e) => {
/* istanbul ignore next */
send(res, 500, errMsg('unknown', e))
})
}
})
if (idServer.conf.additional_features ?? false) {
send(res, 200, { mappings, inactive_mappings: inactives })
} else {
send(res, 200, { mappings })
}
})
.catch((e) => {
/* istanbul ignore next */
send(res, 500, errMsg('unknown', e))
})
}
})
})
}
Expand Down
1 change: 1 addition & 0 deletions packages/matrix-identity-server/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export interface Config {
userdb_user?: string
template_dir: string
check_quota_cron?: string
matrix_server?: string
}

export type DbGetResult = Array<
Expand Down
24 changes: 24 additions & 0 deletions packages/matrix-identity-server/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,27 @@
}
}
}

/**
* Builds a URL from a base URL and a path
*
* @param {string} base - Base URL
* @param {string} path - Path
* @returns {string} - URL
*/
export const buildUrl = (base: string, path: string): string => {
let formattedUrl = base

if (
!formattedUrl.startsWith('https://') &&
!formattedUrl.startsWith('http://')
) {
formattedUrl = `https://${formattedUrl}`
}

const url = new URL(formattedUrl)

url.pathname = path

return url.toString()
}
6 changes: 5 additions & 1 deletion packages/tom-server/src/invitation-api/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,20 +252,24 @@ export default class InvitationService implements IInvitationService {
room_id: string
) => {
try {
const medium = payload.medium === 'phone' ? 'msisdn' : payload.medium

await fetch(buildUrl(this.config.base_url, this.MATRIX_INVITE_PATH), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: authorization
},
body: JSON.stringify({
medium: payload.medium,
medium,
address: payload.recepient,
phone: payload.recepient,
sender: payload.sender,
room_id
})
})
} catch (error) {
console.error({ error })
this.logger.error(`Failed to store matrix invite`, { error })

throw Error('Failed to store matrix invite')
Expand Down
3 changes: 3 additions & 0 deletions server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ if (process.argv[2] === 'generate') {
const appServer = new AppServer(appServerConf)
} else {
const app = express()
app.use(express.json())
app.use(express.urlencoded({ extended: true }))

const trustProxy = process.env.TRUSTED_PROXIES
? process.env.TRUSTED_PROXIES.split(/\s+/)
: []
Expand Down
Loading