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

Added requestToken and submitTokens endpoints for mails #109

Merged
merged 16 commits into from
Jul 12, 2024
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
3 changes: 3 additions & 0 deletions packages/matrix-client-server/src/__testData__/buildUserDB.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ const matrixDbQueries = [
'CREATE TABLE IF NOT EXISTS room_aliases( room_alias TEXT NOT NULL, room_id TEXT NOT NULL, creator TEXT, UNIQUE (room_alias) )',
'CREATE TABLE IF NOT EXISTS rooms( room_id TEXT PRIMARY KEY NOT NULL, is_public BOOL, creator TEXT , room_version TEXT, has_auth_chain_index BOOLEAN)',
'CREATE TABLE IF NOT EXISTS room_tags( user_id TEXT NOT NULL, room_id TEXT NOT NULL, tag TEXT NOT NULL, content TEXT NOT NULL, CONSTRAINT room_tag_uniqueness UNIQUE (user_id, room_id, tag) )',
'CREATE TABLE IF NOT EXISTS "user_threepids" ( user_id TEXT NOT NULL, medium TEXT NOT NULL, address TEXT NOT NULL, validated_at BIGINT NOT NULL, added_at BIGINT NOT NULL, CONSTRAINT medium_address UNIQUE (medium, address) )',
'CREATE TABLE threepid_validation_session (session_id TEXT PRIMARY KEY,medium TEXT NOT NULL,address TEXT NOT NULL,client_secret TEXT NOT NULL,last_send_attempt BIGINT NOT NULL,validated_at BIGINT)',
'CREATE TABLE threepid_validation_token (token TEXT PRIMARY KEY,session_id TEXT NOT NULL,next_link TEXT,expires BIGINT NOT NULL)',
'CREATE TABLE IF NOT EXISTS presence (user_id TEXT NOT NULL, state VARCHAR(20), status_msg TEXT, mtime BIGINT, UNIQUE (user_id))'
]

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{
"cron_service": false,
"database_engine": "sqlite",
"database_host": "./src/__testData__/testRequestToken.db",
"matrix_database_engine": "sqlite",
"matrix_database_host": "./src/__testData__/testMatrixRequestToken.db",
"database_vacuum_delay": 7200,
"is_federated_identity_service": false,
"key_delay": 3600,
"keys_depth": 5,
"mail_link_delay": 7200,
"rate_limiting_window": 10000,
"server_name": "matrix.org",
"smtp_sender": "[email protected]",
"smtp_server": "localhost",
"template_dir": "./templates",
"userdb_engine": "sqlite",
"userdb_host": "./src/__testData__/testRequestToken.db",
"flows": [
{
"stages": ["m.login.dummy"]
},
{
"stages": ["m.login.password", "m.login.registration_token"]
},
{
"stages": ["m.login.terms", "m.login.password"]
},
{
"stages": ["m.login.registration_token", "m.login.dummy"]
}
],
"params": {
"m.login.terms": {
"policies": {
"terms_of_service": {
"version": "1.2",
"en": {
"name": "Terms of Service",
"url": "https://example.org/somewhere/terms-1.2-en.html"
},
"fr": {
"name": "Conditions d'utilisation",
"url": "https://example.org/somewhere/terms-1.2-fr.html"
}
}
}
}
},
"application_services": [
{
"id": "test",
"hs_token": "hsTokenTestwdakZQunWWNe3DZitAerw9aNqJ2a6HVp0sJtg7qTJWXcHnBjgN0NL",
"as_token": "as_token_test",
"url": "http://localhost:3000",
"sender_localpart": "sender_localpart_test",
"namespaces": {
"users": [
{
"exclusive": false,
"regex": "@_irc_bridge_.*"
}
]
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import fs from 'fs'
import {
errMsg,
isValidUrl,
jsonContent,
send,
validateParameters,
type expressAppHandler
} from '@twake/utils'
import type MatrixClientServer from '../../../index'
import Mailer from '../../../utils/mailer'
import {
fillTable,
getSubmitUrl,
preConfigureTemplate
} from '../../../register/email/requestToken'
import { randomString } from '@twake/crypto'

interface RequestTokenArgs {
client_secret: string
email: string
next_link?: string
send_attempt: number
id_server?: string
id_access_token?: string
}

const schema = {
client_secret: true,
email: true,
next_link: false,
send_attempt: true,
id_server: false,
id_access_token: false
}

const clientSecretRe = /^[0-9a-zA-Z.=_-]{6,255}$/
const validEmailRe = /^\w[+.-\w]*\w@\w[.-\w]*\w\.\w{2,6}$/

const RequestToken = (clientServer: MatrixClientServer): expressAppHandler => {
const transport = new Mailer(clientServer.conf)
const verificationTemplate = preConfigureTemplate(
fs
.readFileSync(`${clientServer.conf.template_dir}/mailVerification.tpl`)
.toString(),
clientServer.conf,
transport
)
return (req, res) => {
jsonContent(req, res, clientServer.logger, (obj) => {
validateParameters(res, schema, obj, clientServer.logger, (obj) => {
const clientSecret = (obj as RequestTokenArgs).client_secret
const sendAttempt = (obj as RequestTokenArgs).send_attempt
const dst = (obj as RequestTokenArgs).email
const nextLink = (obj as RequestTokenArgs).next_link
if (!clientSecretRe.test(clientSecret)) {
send(res, 400, errMsg('invalidParam', 'invalid client_secret'))
} else if (!validEmailRe.test(dst)) {
send(res, 400, errMsg('invalidEmail'))
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
} else if (nextLink && !isValidUrl(nextLink)) {
send(res, 400, errMsg('invalidParam', 'invalid next_link'))
} else {
clientServer.matrixDb
.get('user_threepids', ['user_id'], { address: dst })
.then((rows) => {
if (rows.length === 0) {
send(res, 400, errMsg('threepidNotFound'))
} else {
clientServer.matrixDb
.get(
'threepid_validation_session',
['last_send_attempt', 'session_id'],
{
client_secret: clientSecret,
address: dst
}
)
.then((rows) => {
if (rows.length > 0) {
if (sendAttempt === rows[0].last_send_attempt) {
send(res, 200, {
sid: rows[0].session_id,
submit_url: getSubmitUrl(clientServer.conf)
})
} else {
clientServer.matrixDb
.deleteWhere('threepid_validation_session', [
{
field: 'client_secret',
value: clientSecret,
operator: '='
},
{
field: 'session_id',
value: rows[0].session_id as string,
operator: '='
}
])
.then(() => {
fillTable(
clientServer,
dst,
clientSecret,
sendAttempt,
verificationTemplate,
transport,
res,
rows[0].session_id as string,
nextLink
)
})
.catch((err) => {
// istanbul ignore next
clientServer.logger.error('Deletion error', err)
// istanbul ignore next
send(res, 400, errMsg('unknown', err))
})
}
} else {
fillTable(
clientServer,
dst,
clientSecret,
sendAttempt,
verificationTemplate,
transport,
res,
randomString(64),
nextLink
)
}
})
.catch((err) => {
/* istanbul ignore next */
clientServer.logger.error('Send_attempt error', err)
/* istanbul ignore next */
send(res, 400, errMsg('unknown', err))
})
}
})
.catch((err) => {
/* istanbul ignore next */
clientServer.logger.error('Send_attempt error', err)
/* istanbul ignore next */
send(res, 400, errMsg('unknown', err))
})
}
})
})
}
}

export default RequestToken
3 changes: 2 additions & 1 deletion packages/matrix-client-server/src/account/whoami.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ const whoami = (clientServer: MatrixClientServer): expressAppHandler => {
clientServer.matrixDb
.get('users', ['name', 'is_guest'], { name: data.sub })
.then((rows) => {
// istanbul ignore if // might remove the istanbul ignore if an endpoint other than /register modifies the users table
if (rows.length === 0) {
// istanbul ignore next // might remove the istanbul ignore if an endpoint other than /register modifies the users table
send(res, 403, errMsg('invalidUsername'))
return
}
const isGuest = rows[0].is_guest !== 0
const body: responseBody = { user_id: data.sub, is_guest: isGuest }
Expand Down
17 changes: 15 additions & 2 deletions packages/matrix-client-server/src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import fs from 'fs'
import request from 'supertest'
import request, { type Response } from 'supertest'
import express from 'express'
import ClientServer from './index'
import { buildMatrixDb, buildUserDB } from './__testData__/buildUserDB'
Expand Down Expand Up @@ -303,6 +303,20 @@ describe('Use configuration file', () => {
})
describe('/_matrix/client/v3/account/whoami', () => {
let asToken: string
it('should reject if more than 100 requests are done in less than 10 seconds', async () => {
let token
let response
// eslint-disable-next-line @typescript-eslint/no-for-in-array, @typescript-eslint/no-unused-vars
for (const i in [...Array(101).keys()]) {
token = Number(i) % 2 === 0 ? `Bearer ${validToken}` : 'falsy_token'
response = await request(app)
.get('/_matrix/client/v3/account/whoami')
.set('Authorization', token)
.set('Accept', 'application/json')
}
expect((response as Response).statusCode).toEqual(429)
await new Promise((resolve) => setTimeout(resolve, 11000))
})
it('should reject missing token (', async () => {
const response = await request(app)
.get('/_matrix/client/v3/account/whoami')
Expand Down Expand Up @@ -716,7 +730,6 @@ describe('Use configuration file', () => {
},
username: '@localhost:example.com'
})
console.log(response.body)
expect(response.statusCode).toBe(400)
expect(response.body).toHaveProperty('error')
expect(response.body).toHaveProperty('errcode', 'M_INVALID_USERNAME')
Expand Down
28 changes: 25 additions & 3 deletions packages/matrix-client-server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ import {
setRoomVisibility
} from './rooms/room_information/room_visibilty'
import { getRoomAliases } from './rooms/room_information/room_aliases'
import RequestTokenPasswordEmail from './account/password/email/requestToken'
import RequestTokenEmail from './register/email/requestToken'
import SubmitTokenEmail from './register/email/submitToken'
import getTimestampToEvent from './rooms/roomId/getTimestampToEvent'
import getStatus from './presence/getStatus'
import putStatus from './presence/putStatus'
Expand Down Expand Up @@ -144,6 +147,10 @@ export default class MatrixClientServer extends MatrixIdentityServer<clientDbCol
'/_matrix/client/v3/directory/list/room/:roomId':
getRoomVisibility(this),
'/_matrix/client/v3/rooms/:roomId/aliases': getRoomAliases(this),
'/_matrix/client/v3/account/password/email/requestToken': badMethod,
'/_matrix/client/v3/register/email/requestToken': badMethod,
'/_matrix/client/v3/register/email/submitToken':
SubmitTokenEmail(this),
'/_matrix/client/v3/rooms/:roomId/timestamp_to_event':
getTimestampToEvent(this),
'/_matrix/client/v3/presence/:userId/status': getStatus(this)
Expand All @@ -165,7 +172,14 @@ export default class MatrixClientServer extends MatrixIdentityServer<clientDbCol
'/_matrix/client/v3/user/:userId/rooms/:roomId/tags': badMethod,
'/_matrix/client/v3/joined_rooms': badMethod,
'/_matrix/client/v3/directory/list/room/:roomId': badMethod,
'/_matrix/client/v3/rooms/{roomId}/aliases': badMethod,
'/_matrix/client/v3/rooms/:roomId/aliases': badMethod,
'/_matrix/client/v3/account/password/email/requestToken':
RequestTokenPasswordEmail(this),
'/_matrix/client/v3/register/email/requestToken':
RequestTokenEmail(this),
'/_matrix/client/v3/register/email/submitToken':
SubmitTokenEmail(this),
'/_matrix/client/v3/rooms/:roomId/timestamp_to_event': badMethod,
'/_matrix/client/v3/user/:roomId/timestamp_to_event': badMethod,
'/_matrix/client/v3/presence/:userId/status': badMethod
}
Expand All @@ -192,7 +206,11 @@ export default class MatrixClientServer extends MatrixIdentityServer<clientDbCol
'/_matrix/client/v3/joined_rooms': badMethod,
'/_matrix/client/v3/directory/list/room/:roomId':
setRoomVisibility(this),
'/_matrix/client/v3/rooms/{roomId}/aliases': badMethod,
'/_matrix/client/v3/rooms/:roomId/aliases': badMethod,
'/_matrix/client/v3/account/password/email/requestToken': badMethod,
'/_matrix/client/v3/register/email/requestToken': badMethod,
'/_matrix/client/v3/register/email/submitToken': badMethod,
'/_matrix/client/v3/rooms/:roomId/timestamp_to_event': badMethod,
'/_matrix/client/v3/user/:roomId/timestamp_to_event': badMethod,
'/_matrix/client/v3/presence/:userId/status': putStatus(this)
}
Expand All @@ -210,7 +228,11 @@ export default class MatrixClientServer extends MatrixIdentityServer<clientDbCol
removeUserRoomTag(this),
'/_matrix/client/v3/joined_rooms': badMethod,
'/_matrix/client/v3/directory/list/room/:roomId': badMethod,
'/_matrix/client/v3/rooms/{roomId}/aliases': badMethod,
'/_matrix/client/v3/rooms/:roomId/aliases': badMethod,
'/_matrix/client/v3/account/password/email/requestToken': badMethod,
'/_matrix/client/v3/register/email/requestToken': badMethod,
'/_matrix/client/v3/register/email/submitToken': badMethod,
'/_matrix/client/v3/rooms/:roomId/timestamp_to_event': badMethod,
'/_matrix/client/v3/presence/:userId/status': badMethod
}
resolve(true)
Expand Down
Loading
Loading