p2p https and login fix, removed static domain from ejs files

This commit is contained in:
mrfry 2023-04-07 16:32:22 +02:00
parent ae3bd7c55a
commit e858d7f23e
13 changed files with 210 additions and 79 deletions

View file

@ -69,6 +69,8 @@ To setup P2P functionality you have to create a few files in `./data/p2p`:
* `peers.json` : an array, with objects same as above, and `{ publicKey: "public key of the server" * `peers.json` : an array, with objects same as above, and `{ publicKey: "public key of the server"
}`. Public key is used to encrypt the users database in the response, so they can be synced too. }`. Public key is used to encrypt the users database in the response, so they can be synced too.
Extra configuration: HTTP and pw! TODO
Uppon syncing data or having a peer request data from your server there will be new entries in Uppon syncing data or having a peer request data from your server there will be new entries in
`./data/p2p/thirdPartyPeers.json`. Here you can review the peers, see their contact and host, and if `./data/p2p/thirdPartyPeers.json`. Here you can review the peers, see their contact and host, and if
you choose you can add them to your `peers.json` file. `thirdPartyPeers.json` should also contain you choose you can add them to your `peers.json` file. `thirdPartyPeers.json` should also contain
@ -138,7 +140,6 @@ needed at all
| NS_THREAD_COUNT | number | Nubmer of CPU cores to use | | NS_THREAD_COUNT | number | Nubmer of CPU cores to use |
| NS_NOUSER | boolean | If the authorization should be skipped (for testing) | | NS_NOUSER | boolean | If the authorization should be skipped (for testing) |
| NS_NO_HTTPS_FORCE | boolean | Disables automatic redirects from http to https | | NS_NO_HTTPS_FORCE | boolean | Disables automatic redirects from http to https |
| NS_DEVEL | boolean | Developemnt mode. Now it only forces login page to use localhost |
| NS_LOGLEVEL | number | Debug log level, 0 is the least verbose | | NS_LOGLEVEL | number | Debug log level, 0 is the least verbose |
| NS_NOLOG | boolean | If logging should be skipped | | NS_NOLOG | boolean | If logging should be skipped |
| NS_SQL_DEBUG_LOG | boolean | If the SQL queries should be logged | | NS_SQL_DEBUG_LOG | boolean | If the SQL queries should be logged |

View file

@ -20,7 +20,7 @@
}, },
"scripts": { "scripts": {
"start": "node ./dist/server.js", "start": "node ./dist/server.js",
"dev": "npm run build && NS_THREAD_COUNT=2 NS_DEVEL=1 NS_NOUSER=1 node --inspect ./dist/server.js", "dev": "npm run build && NS_NO_HTTPS_FORCE=1 NS_THREAD_COUNT=2 NS_NOUSER=1 node --inspect ./dist/server.js",
"build": "tsc && bash -c './scripts/postBuild.sh'", "build": "tsc && bash -c './scripts/postBuild.sh'",
"export": "tsc && bash -c './scripts/postBuild.sh'", "export": "tsc && bash -c './scripts/postBuild.sh'",
"test": "NS_NOLOG=1 NS_THREAD_COUNT=1 jest", "test": "NS_NOLOG=1 NS_THREAD_COUNT=1 jest",

View file

@ -24,6 +24,10 @@ import type { Database } from 'better-sqlite3'
import logger from '../utils/logger' import logger from '../utils/logger'
import dbtools from '../utils/dbtools' import dbtools from '../utils/dbtools'
import { paths } from '../utils/files'
import utils from '../utils/utils'
const domain = utils.ReadFile(paths.domainFile).trim()
interface Options { interface Options {
userDB: Database userDB: Database
@ -31,7 +35,7 @@ interface Options {
} }
export const testUser: User = { export const testUser: User = {
id: 19, id: 1,
avaiblePWRequests: 645, avaiblePWRequests: 645,
pwRequestCount: 19, pwRequestCount: 19,
created: new Date().getTime(), created: new Date().getTime(),
@ -51,7 +55,8 @@ function renderLogin(req: Request, res: Response) {
}) })
} else { } else {
res.render('login', { res.render('login', {
devel: process.env.NS_DEVEL, useHttp: process.env.NS_NO_HTTPS_FORCE,
domain: domain,
}) })
} }
} }

View file

@ -105,7 +105,7 @@ function GetApp(): ModuleType {
function reloadRootRedirectURL() { function reloadRootRedirectURL() {
if (utils.FileExists(paths.rootRedirectToFile)) { if (utils.FileExists(paths.rootRedirectToFile)) {
rootRedirectURL = utils.ReadFile(paths.rootRedirectToFile) rootRedirectURL = utils.ReadFile(paths.rootRedirectToFile).trim()
} }
} }

View file

@ -19,7 +19,6 @@
------------------------------------------------------------------------- */ ------------------------------------------------------------------------- */
import { Response } from 'express' import { Response } from 'express'
import http from 'http'
import logger from '../../../utils/logger' import logger from '../../../utils/logger'
import { import {
@ -60,6 +59,7 @@ import {
SelfInfoSchema, SelfInfoSchema,
} from '../../../types/typeSchemas' } from '../../../types/typeSchemas'
import { paths } from '../../../utils/files' import { paths } from '../../../utils/files'
import { GetResult, get, post } from '../../../utils/networkUtils'
interface MergeResult { interface MergeResult {
newData: Subject[] newData: Subject[]
@ -87,47 +87,17 @@ interface RemotePeerInfo {
} }
} }
interface RequestResult<T> {
data?: T
error?: Error
options?: http.RequestOptions
}
interface SyncDataRes { interface SyncDataRes {
questionDbs?: QuestionDb[] questionDbs?: QuestionDb[]
remoteInfo?: RemotePeerInfo remoteInfo?: RemotePeerInfo
encryptedUsers?: string encryptedUsers?: string
count: { count?: {
qdbs: number qdbs: number
subjects: number subjects: number
questions: number questions: number
} }
} }
// FIXME: to utils/http.ts
function get<T>(options: http.RequestOptions): Promise<RequestResult<T>> {
return new Promise((resolve) => {
const req = http.get(options, function (res) {
const bodyChunks: Uint8Array[] = []
res.on('data', (chunk) => {
bodyChunks.push(chunk)
}).on('end', () => {
const body = Buffer.concat(bodyChunks).toString()
try {
resolve({ data: JSON.parse(body) })
} catch (e) {
console.log(body)
resolve({ error: e, options: options })
}
})
})
req.on('error', function (e) {
resolve({ error: e, options: options })
// reject(e)
})
})
}
function updateThirdPartyPeers( function updateThirdPartyPeers(
newVal: Omit<PeerInfo, 'publicKey' | 'name' | 'contact'>[] newVal: Omit<PeerInfo, 'publicKey' | 'name' | 'contact'>[]
) { ) {
@ -341,6 +311,54 @@ function setupQuestionsForMerge(qdb: QuestionDb, peer: PeerInfo) {
} }
} }
async function authAndGetNewData({
peer,
selfInfo,
lastSyncWithPeer,
lastSync,
}: {
peer: PeerInfo
selfInfo: PeerInfo
lastSyncWithPeer: number
lastSync: number
}): Promise<GetResult<SyncDataRes & { peer: PeerInfo }>> {
const { data, error, cookies } = await post<{
result: string
msg: string
}>({
hostname: peer.host,
path: '/api/login',
port: peer.port,
bodyObject: { pw: peer.pw },
http: peer.http,
})
if (error || !data || data.result !== 'success') {
return {
error: data ? new Error(data.msg) : error,
data: {
peer: peer,
},
}
}
const getRes = await get<SyncDataRes>(
{
headers: {
cookie: cookies.join(),
},
host: peer.host,
port: peer.port,
path: `/api/getnewdatasince?host=${encodeURIComponent(
peerToString(selfInfo)
)}${lastSync ? `&since=${lastSyncWithPeer}` : ''}`,
},
peer.http
)
return { ...getRes, data: { ...getRes.data, peer: peer } }
}
function setup(data: SubmoduleData): Submodule { function setup(data: SubmoduleData): Submodule {
const { const {
app, app,
@ -453,16 +471,20 @@ function setup(data: SubmoduleData): Submodule {
// FUNCTIONS // FUNCTIONS
// --------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------
function getSelfInfo(includeQdbInfo?: boolean) { function getSelfInfo(includeVerboseInfo?: boolean) {
const result: RemotePeerInfo = { const result: RemotePeerInfo = {
selfInfo: selfInfo, selfInfo: selfInfo,
myPeers: peers, myPeers: peers,
} }
if (includeVerboseInfo) {
result.serverRevision = utils.getGitRevision(__dirname) result.serverRevision = utils.getGitRevision(__dirname)
result.scriptRevision = utils.getGitRevision( result.scriptRevision = utils.getGitRevision(
paths.moodleTestUserscriptDir paths.moodleTestUserscriptDir
) )
result.qminingPageRevision = utils.getGitRevision(paths.qminingPageDir) result.qminingPageRevision = utils.getGitRevision(
paths.qminingPageDir
)
result.dataEditorRevision = utils.getGitRevision( result.dataEditorRevision = utils.getGitRevision(
paths.dataEditorPageDir paths.dataEditorPageDir
) )
@ -478,7 +500,6 @@ function setup(data: SubmoduleData): Submodule {
result.scriptVersion = utils.getScriptVersion() result.scriptVersion = utils.getScriptVersion()
result.userCount = dbtools.TableInfo(userDB, 'users').dataCount result.userCount = dbtools.TableInfo(userDB, 'users').dataCount
if (includeQdbInfo) {
const questionDbCount = getQuestionDbs().length const questionDbCount = getQuestionDbs().length
const { subjCount, questionCount } = countOfQdbs(getQuestionDbs()) const { subjCount, questionCount } = countOfQdbs(getQuestionDbs())
result.qdbInfo = { result.qdbInfo = {
@ -585,19 +606,12 @@ function setup(data: SubmoduleData): Submodule {
const requests = peers.map((peer) => { const requests = peers.map((peer) => {
const lastSyncWithPeer = peer.lastSync || 0 const lastSyncWithPeer = peer.lastSync || 0
return new Promise<RequestResult<SyncDataRes & { peer: PeerInfo }>>( return authAndGetNewData({
(resolve) => { peer: peer,
get<SyncDataRes>({ selfInfo: selfInfo,
host: peer.host, lastSyncWithPeer: lastSyncWithPeer,
port: peer.port, lastSync: lastSync,
path: `/getnewdatasince?host=${encodeURIComponent(
peerToString(selfInfo)
)}${lastSync ? `&since=${lastSyncWithPeer}` : ''}`,
}).then((res) => {
resolve({ ...res, data: { ...res.data, peer: peer } })
}) })
}
)
}) })
const allResults = await Promise.all(requests) const allResults = await Promise.all(requests)
@ -912,6 +926,10 @@ function setup(data: SubmoduleData): Submodule {
// --------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------
// APP SETUP // APP SETUP
// --------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------
app.get('/selfInfo', (_req: Request, res: Response<PeerInfo>) => {
res.json(selfInfo)
})
app.get('/p2pinfo', (_req: Request, res: Response<RemotePeerInfo>) => { app.get('/p2pinfo', (_req: Request, res: Response<RemotePeerInfo>) => {
res.json(getSelfInfo(true)) res.json(getSelfInfo(true))
}) })

View file

@ -68,7 +68,7 @@ function createDefaultUser(userDb: Database) {
const pw = uuidv4() const pw = uuidv4()
const insertRes = dbtools.Insert(userDb, 'users', { const insertRes = dbtools.Insert(userDb, 'users', {
pw: pw, pw: pw,
avaiblePWRequests: 0, avaiblePWRequests: 50,
created: utils.GetDateString(), created: utils.GetDateString(),
}) })
logger.Log('ID and PW for user #1: ', 'yellowbg') logger.Log('ID and PW for user #1: ', 'yellowbg')

View file

@ -267,7 +267,7 @@ app.get('*', (req, res) => {
if (req.is('application/json')) { if (req.is('application/json')) {
res.status(404).end() res.status(404).end()
} else { } else {
res.status(404).render('404') res.status(404).render('404', { domain: domain })
} }
}) })

View file

@ -3,7 +3,7 @@
<body bgcolor="#222426"> <body bgcolor="#222426">
<head> <head>
<title>Nem található - Qmining | Frylabs.net</title> <title>Qmining | <%= domain %> - 404</title>
<style> <style>
@import url('https://fonts.googleapis.com/css2?family=Kameron&family=Overpass+Mono:wght@300;400&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Kameron&family=Overpass+Mono:wght@300;400&display=swap');
body { body {

View file

@ -1,7 +1,7 @@
<html> <html>
<body bgcolor="#222426"> <body bgcolor="#222426">
<head> <head>
<title>Qmining | Frylabs.net - Login</title> <title>Qmining | <%= domain %> - Login</title>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=0.6" /> <meta name="viewport" content="width=device-width, initial-scale=0.6" />
<style> <style>
@ -123,7 +123,7 @@
button.classList.add('disabledButton') button.classList.add('disabledButton')
button.disabled = true button.disabled = true
// TODO: get url from controller // TODO: get url from controller
const rawResponse = await fetch('<%= devel ? 'http://localhost:8080/api/login' : 'https://frylabs.net/api/login' %>', { const rawResponse = await fetch('<%= useHttp ? `http://${domain}/api/login` : `https://${domain}/api/login` %>', {
method: 'POST', method: 'POST',
credentials: 'include', credentials: 'include',
headers: { headers: {

View file

@ -98,6 +98,7 @@ export interface User {
lastLogin: number lastLogin: number
lastAccess: number lastAccess: number
sourceHost?: number sourceHost?: number
// isAdmin: boolean // TODO
} }
export interface Request<T = any> extends express.Request { export interface Request<T = any> extends express.Request {
@ -174,6 +175,8 @@ export interface PeerInfo {
port: number port: number
publicKey: string publicKey: string
contact: string contact: string
pw?: string
lastSync?: number lastSync?: number
note?: string note?: string
http?: boolean
} }

View file

@ -46,8 +46,10 @@ const PeerInfoSchemaBase = {
contact: { type: 'string' }, contact: { type: 'string' },
lastSync: { type: 'number' }, lastSync: { type: 'number' },
note: { type: 'string' }, note: { type: 'string' },
pw: { type: 'string' },
http: { type: 'boolean' },
}, },
required: ['name', 'host', 'port'], required: ['name', 'host', 'port', 'contact', 'pw'],
} }
export const SelfInfoSchema: Schema = { export const SelfInfoSchema: Schema = {

View file

@ -3,8 +3,8 @@ import {
TestUsersSchema, TestUsersSchema,
isJsonValidAndLogError, isJsonValidAndLogError,
PeersInfoSchema, PeersInfoSchema,
PeerInfoSchema,
ModulesSchema, ModulesSchema,
SelfInfoSchema,
} from '../types/typeSchemas' } from '../types/typeSchemas'
import logger from './logger' import logger from './logger'
import utils from './utils' import utils from './utils'
@ -230,7 +230,7 @@ export const files = {
path: 'data/p2p/selfInfo.json', path: 'data/p2p/selfInfo.json',
description: 'json of info of this servers peer functionality', description: 'json of info of this servers peer functionality',
defaultValue: JSON.stringify({}), defaultValue: JSON.stringify({}),
schema: PeerInfoSchema, schema: SelfInfoSchema,
}, },
thirdPartyPeersFile: { thirdPartyPeersFile: {
path: 'data/p2p/thirdPartyPeers.json', path: 'data/p2p/thirdPartyPeers.json',

102
src/utils/networkUtils.ts Normal file
View file

@ -0,0 +1,102 @@
import http, { request as httpRequest } from 'http'
import https, { request as httpsRequest } from 'https'
export interface GetResult<T> {
data?: T
error?: Error
options?: http.RequestOptions
}
export function get<T = any>(
options: http.RequestOptions,
useHttp?: boolean
): Promise<GetResult<T>> {
const provider = useHttp ? http : https
return new Promise((resolve) => {
const req = provider.get(options, function (res) {
const bodyChunks: Uint8Array[] = []
res.on('data', (chunk) => {
bodyChunks.push(chunk)
}).on('end', () => {
const body = Buffer.concat(bodyChunks).toString()
try {
resolve({ data: JSON.parse(body) })
} catch (e) {
console.log(body)
resolve({ error: e, options: options })
}
})
})
req.on('error', function (e) {
resolve({ error: e, options: options })
})
})
}
export interface PostResult<T> {
data?: T
error?: Error
cookies?: string[]
}
interface PostParams {
hostname: string
path: string
port: number
bodyObject: any
http?: boolean
}
// https://nodejs.org/api/http.html#httprequesturl-options-callback
export function post<T = any>({
hostname,
path,
port,
bodyObject,
http,
}: PostParams): Promise<PostResult<T>> {
const provider = http ? httpRequest : httpsRequest
const body = JSON.stringify(bodyObject)
return new Promise((resolve) => {
const req = provider(
{
hostname: hostname,
port: port,
path: path,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(body),
},
},
(res) => {
const bodyChunks: string[] = []
res.setEncoding('utf8')
res.on('data', (chunk) => {
bodyChunks.push(chunk)
})
res.on('end', () => {
const body = bodyChunks.join()
try {
resolve({
data: JSON.parse(body),
cookies: res.headers['set-cookie'],
})
} catch (e) {
console.log(body)
resolve({ error: e })
}
})
}
)
req.on('error', (e) => {
resolve({ error: e })
})
req.write(body)
req.end()
})
}