Skip to content
Open
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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
dist
node_modules
tmp
tmp
tests
db/
53 changes: 53 additions & 0 deletions bin/authenticators/http.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const authCallback = process.env.YWEBSOCKET_HTTP_AUTH_CALLBACK || 'http://localhost/auth/'
const authCallbackParam = process.env.YWEBSOCKET_HTTP_AUTH_CALLBACK_GET_PARAM || 'code'
const querystring = require('querystring')

module.exports = {
authenticate: function (request) {
return new Promise(function (resolve, reject) {
const docName = request.url.substring(1)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Если честно, довольно непонятно, что именно тут происходит.

Непонятно, что за request.url.substring(1). Предполагаю, что это накладывает определённые условия на request.url? Какой url ожидается изначально?

Что должно храниться в authCallback и что в итоге получится в результате authCallback + docName? Получается, что authCallback тоже должно быть очень определённого формата, так?

Плюс тут, кажется, есть возможность устроить какой-нибудь хак, типа, негодяй в request.url передаст что-то вроде ../../../api/v1/something-that-always-returns-status-ok/ и получит какой-то несанкционированный доступ.

Короче, напрягает, что тут происходит что-то, что сильно полагается на символы в строчках, и что весь процесс никак не прокомментирован, а из кода понять — сложно.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

request.url тут всегда docName, если умышленно не передать что-то другое. Если какой-то негодяй передаст что угодно,то это что угодно просто передастся get-параметром к авторизационному урлу, то получится, просто левое значение.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А как бы ты передал значение docName?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А как бы ты передал значение docName?

Написал вот в этом комменте. По сути на самом деле так же, конечно, но слегка аккуратнее.

const docName = request.url.substring(1)

Тут я бы предложил вынести в функцию, чтобы её же задокументировать, и чтобы в логику не просачивались детали реализации, а если в ней будем обнаруживать баги (например, вдруг в каких-то условиях в request.url передастся полный урл?), то там усложним и тестами покроем. Ну или абстрагируем вовсе. Но в любом случае будущее — это про YAGNI, а в настоящем важны документирование и семантика:

...

/*


*/
function getDocNameFromRequest(request) {
    const docName = request.url.substring(1)
    return docName
}

...

const docName = getDocNameFromRequest(request)

(вот тут бы питоновские доктесты были бы идеальны :–)


А ещё я не нашёл в документации request.url. Куда смотреть? Хочу убедиться, что он там действительно относительный, и мы не окажемся с docName вида "ttps://...:" :–)

Может, уместнее будет использовать request.path?

const query = querystring.stringify({
[authCallbackParam]: docName
})
let authRequester
if (authCallback.indexOf('https:') === 0) {
authRequester = require('https')
} else {
authRequester = require('http')
}
const authCallbackWithRoomCode = authCallback + '?' + query
console.log(authCallbackWithRoomCode)
const authRequest = authRequester.request(
authCallbackWithRoomCode,
{
method: 'GET',
headers: {
Cookie: request.headers.cookie || ''
}
},
response => {
if (response.statusCode < 200 || response.statusCode >= 300) {
return reject(new Error('statusCode=' + response.statusCode))
}
response.setEncoding('utf8')
let rawData = ''
response.on('data', chunk => {
console.log(chunk)
rawData += chunk
})
response.on('end', () => {
const data = JSON.parse(rawData)
console.log(rawData)
if (data.status === 'ok') {
resolve(true)
}
})
}
)
authRequest.on('error', function (error) {
reject(error)
})
authRequest.end()
})
}
}
10 changes: 9 additions & 1 deletion bin/collaborate-editing-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const setupWSConnection = require('./utils.js').setupWSConnection

const host = process.env.YWEBSOCKET_HOST || 'localhost'
const port = process.env.YWEBSOCKET_PORT || 1234
const authenticator = process.env.YWEBSOCKET_AUTHENTICATOR || null
Comment thread
anatolyrr marked this conversation as resolved.

const server = http.createServer((request, response) => {
response.writeHead(200, { 'Content-Type': 'application/json' })
Expand All @@ -22,7 +23,14 @@ server.on('upgrade', (request, socket, head) => {
* @param {any} ws
*/
const handleAuth = ws => {
wss.emit('connection', ws, request)
if (authenticator) {
const authenticate = require('./authenticators/' + authenticator).authenticate
authenticate(request).then(() => {
wss.emit('connection', ws, request)
})
} else {
wss.emit('connection', ws, request)
}
}
wss.handleUpgrade(request, socket, head, handleAuth)
})
Expand Down
23 changes: 23 additions & 0 deletions bin/connections/mysql.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const mysql = require('mysql2')

const MYSQL_HOST = process.env.MYSQL_HOST || 'localhost'
const MYSQL_PORT = process.env.MYSQL_PORT || 3306
const MYSQL_USER = process.env.MYSQL_USER || 'root'
const MYSQL_PASSWORD = process.env.MYSQL_PASSWORD || ''
const MYSQL_DATABASE = process.env.MYSQL_DATABASE || 'database'

const mysqlConnection = mysql.createConnection({
host: MYSQL_HOST,
user: MYSQL_USER,
database: MYSQL_DATABASE,
password: MYSQL_PASSWORD,
port: MYSQL_PORT
})

mysqlConnection.connect(function (error) {
if (error) throw error
})

module.exports = {
mysqlConnection: mysqlConnection
}
4 changes: 1 addition & 3 deletions bin/note-synchronizer-server.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
const redisPersistenceBridge = require('./redis-persistence-bridge')

const http = require('http')

const host = process.env.YPERSISTENCE_SYNCHRONIZER_HOST || 'localhost'
Expand All @@ -16,7 +14,7 @@ const server = http.createServer((request, response) => {
request.on('end', function () {
const post = JSON.parse(body)
for (const sharedObject of post.sharedObjects) {
redisPersistenceBridge.saveQuillDeltaToRedis(post.documentName, sharedObject.name, sharedObject.delta)
// redisPersistenceBridge.saveQuillDeltaToRedis(post.documentName, sharedObject.name, sharedObject.delta)
}
})
}
Expand Down
20 changes: 1 addition & 19 deletions bin/transfer-data-from-mysql-to-redis.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,11 @@
#!/usr/bin/env node

const redisPersistenceBridge = require('./redis-persistence-bridge')
const mysqlConnection = require('../connections/mysql').mysqlConnection

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Предполагаю, что это было вытащено для авторизации через django_session? Думаю, сейчас можно втащить обратно. Кажется, ни к чему плодить кучи файлов, не?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Опять-же, потому что это библиотека, а у кого-то может быть Postgres или Mongo.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Если у него Postgres или Mongo, то и скрипт будет называться transfer-data-from-postrgres-или-mongo-to-redis. И SQL-запросы в этом скрипте будут другие.

Короче, я к тому, что этот скрипт сам по себе очень специфичен для mysql и не вижу причин делить его на два файла.

Либо надо его тоже абстрагировать: transfer-data-from-X-to-Y и настраивать коннекторы… Но это уже, кажется, совсем перебор.

К слову, раз мы вернули level-db, надо, видимо, и эту механику адаптировать, м?


const mysql = require('mysql2')

const MYSQL_HOST = process.env.MYSQL_HOST || 'localhost'
const MYSQL_USER = process.env.MYSQL_USER || 'root'
const MYSQL_PASSWORD = process.env.MYSQL_PASSWORD || ''
const MYSQL_DATABASE = process.env.MYSQL_DATABASE || 'database'
const MYSQL_TABLE = process.env.MYSQL_TABLE || 'table'
const MYSQL_CONTENT_FIELDS = process.env.MYSQL_CONTENT_FIELDS || 'content'
const MYSQL_KEY_FIELD = process.env.MYSQL_KEY_FIELD || 'id'
const MYSQL_PORT = process.env.MYSQL_PORT || 3306

const mysqlConnection = mysql.createConnection({
host: MYSQL_HOST,
user: MYSQL_USER,
database: MYSQL_DATABASE,
password: MYSQL_PASSWORD,
port: MYSQL_PORT
})

mysqlConnection.connect(function (error) {
if (error) throw error
})

mysqlConnection.query(
`SELECT ${MYSQL_KEY_FIELD}, ${MYSQL_CONTENT_FIELDS} FROM ${MYSQL_TABLE}`,
Expand Down
29 changes: 9 additions & 20 deletions bin/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,36 +22,25 @@ const wsReadyStateClosed = 3 // eslint-disable-line

// disable gc when using snapshots!
const gcEnabled = process.env.GC !== 'false' && process.env.GC !== '0'
const persistenceStorage = process.env.YPERSISTENCE_STORAGE || 'level-db'
const persistenceHost = process.env.YPERSISTENCE_REDIS_HOST || 'localhost'
const persistencePort = process.env.YPERSISTENCE_REDIS_PORT || 6379
const persistencePath = process.env.YPERSISTENCE_LEVELDB_PATH || 'db'

const persistenceDir = process.env.YPERSISTENCE_LEVELDB_PATH
/**
* @type {{bindState: function(string,WSSharedDoc):void, writeState:function(string,WSSharedDoc):Promise<any>, provider: any}|null}
*/
let persistence = null
if (typeof persistencePath === 'string') {
if (typeof persistenceDir === 'string') {
console.info('Persisting documents to "' + persistenceDir + '"')
// @ts-ignore
let persistenceDB
if (persistenceStorage === 'redis') {
console.info('Persisting documents to "' + persistenceHost + '"')
const RedisPersistence = require('y-redis').RedisPersistence
persistenceDB = new RedisPersistence({ redisOpts: { host: persistenceHost, port: persistencePort } })
} else {
console.info('Persisting documents to "' + persistencePath + '"')
const LeveldbPersistence = require('y-leveldb').LeveldbPersistence
persistenceDB = new LeveldbPersistence(persistencePath)
}
const LeveldbPersistence = require('y-leveldb').LeveldbPersistence
const ldb = new LeveldbPersistence(persistenceDir)
persistence = {
provider: persistenceDB,
provider: ldb,
bindState: async (docName, ydoc) => {
const persistedYdoc = await persistenceDB.getYDoc(docName)
const persistedYdoc = await ldb.getYDoc(docName)
const newUpdates = Y.encodeStateAsUpdate(ydoc)
persistenceDB.storeUpdate(docName, newUpdates)
ldb.storeUpdate(docName, newUpdates)
Y.applyUpdate(ydoc, Y.encodeStateAsUpdate(persistedYdoc))
ydoc.on('update', update => {
persistenceDB.storeUpdate(docName, update)
ldb.storeUpdate(docName, update)
})
},
writeState: async (docName, ydoc) => {}
Expand Down