Compare commits

..

No commits in common. '1f3cfa148b6578fb2a19ec5696cc66afc782b251' and 'e689be1b42a79e95fa3ca3daa8ac9e5f862e3361' have entirely different histories.

7
.gitignore vendored

@ -134,6 +134,7 @@ storage/*
!storage/logs/.gitkeep !storage/logs/.gitkeep
!storage/.gitkeep !storage/.gitkeep
custom resources/actions/*
!custom/.gitkeep resources/schemas/*
!custom/package-custom.json !resources/actions/.gitkeep
!resources/schemas/.gitkeep

@ -1,7 +1,7 @@
# Signpost / Xmpp # Signpost / Xmpp
Using Signpost to send messages for Debugging, as Warning, status of Deploy or Critical Errors from Using Signpost to send you messages for Debugging, as Warning, status of Deploy or Critical Errors from
Services or Server to an Xmpp-Server. Services or Server.
## Messages ## Messages
@ -21,19 +21,3 @@ curl -X POST https://<domain>/api/flow/v1/<uuid>
-H 'Authorization: Bearer <token>' -H 'Authorization: Bearer <token>'
-d '{"message":"<text>"}' -d '{"message":"<text>"}'
``` ```
```
curl -X POST https://<domain>/api/flow/v1/<uuid>/token
-H 'Content-Type: application/json'
-d '{"message":"<text>"}'
```
### PHP
### Nodejs
## Messenger
https://gajim.org/
https://pidgin.im/
https://profanity-im.github.io/
https://dino.im/

@ -1,5 +0,0 @@
{
"private": true,
"name": "custom",
"type": "module"
}

@ -1,32 +0,0 @@
import Action from './../../packages/server/actions/action.js'
import XmppHelper from './../helpers/Xmpp.js'
/**
* Xmpp Message
*
* @author Björn Hase <me@herr-hase.wtf>
* @license http://opensource.org/licenses/MIT The MIT License
* @link https://git.node001.net/HerrHase/super-hog.git
*
*/
class Xmpp extends Action {
/**
*
*
*/
async run() {
const xmppHelper = new XmppHelper(
process.env.XMPP_SERVICE,
process.env.XMPP_DOMAIN,
process.env.XMPP_USERNAME,
process.env.XMPP_PASSWORD
)
await xmppHelper.sendToRoom(this.flow, process.env.XMPP_ROOM, this.data.message)
}
}
export default Xmpp

@ -1,80 +0,0 @@
import { client, xml } from '@xmpp/client'
import debug from '@xmpp/debug'
import DOMPurify from 'isomorphic-dompurify'
import Action from './../../packages/server/actions/action.js'
import logger from './../../packages/server/helper/logger.js'
/**
*
*
* @author Björn Hase <me@herr-hase.wtf>
* @license http://opensource.org/licenses/MIT The MIT License
* @link https://git.node001.net/HerrHase/super-hog.git
*
*/
class XmppHelper {
/**
*
*
*/
constructor(service, domain, username, password) {
this.service = service
this.domain = domain
this.username = username
this.password = password
}
/**
*
*
*/
async sendToRoom(flow, room, message) {
// cleaning data
message = DOMPurify.sanitize(message)
const xmpp = client({
service: this.service,
domain: this.domain,
username: this.username,
password: this.password
})
if (process.env.APP_DEBUG) {
debug(xmpp, true)
}
// handle if client has errors
xmpp.on('error', (error) => {
logger(flow.uuid).error('xmpp / error ' + error)
})
// handle if client goes online
xmpp.once('online', async (address) => {
// join group
await xmpp.send(xml("presence", { to: room + '/' + this.username }, xml("x", { xmlns: 'http://jabber.org/protocol/muc' })))
// sends a message to the room
const message = xml(
'message', {
type: 'groupchat',
to: room
},
xml('body', {}, message)
)
await xmpp.send(message)
logger(flow.uuid).info('xmpp / message send')
await xmpp.disconnect()
})
await xmpp.start()
}
}
export default XmppHelper

21
package-lock.json generated

@ -1,25 +1,14 @@
{ {
"name": "signpost", "name": "signpost",
"version": "0.1.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "signpost", "name": "signpost",
"version": "0.1.0",
"workspaces": [ "workspaces": [
"packages/*", "packages/*"
"custom"
] ]
}, },
"custom": {
"dependencies": {
"@xmpp/client": "^0.14.0"
},
"devDependencies": {
"@xmpp/debug": "^0.14.0"
}
},
"node_modules/@acemir/cssom": { "node_modules/@acemir/cssom": {
"version": "0.9.31", "version": "0.9.31",
"resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.31.tgz", "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.31.tgz",
@ -1323,10 +1312,6 @@
"node": ">=20" "node": ">=20"
} }
}, },
"node_modules/custom": {
"resolved": "custom",
"link": true
},
"node_modules/data-urls": { "node_modules/data-urls": {
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz",
@ -2677,6 +2662,7 @@
"dependencies": { "dependencies": {
"@fastify/rate-limit": "^10.3.0", "@fastify/rate-limit": "^10.3.0",
"@inquirer/prompts": "^8.3.0", "@inquirer/prompts": "^8.3.0",
"@xmpp/client": "^0.14.0",
"better-sqlite3": "^12.6.2", "better-sqlite3": "^12.6.2",
"chalk": "^5.6.2", "chalk": "^5.6.2",
"dayjs": "^1.11.19", "dayjs": "^1.11.19",
@ -2686,6 +2672,9 @@
"pino": "^10.3.1", "pino": "^10.3.1",
"pino-pretty": "^13.1.3", "pino-pretty": "^13.1.3",
"uuid": "^13.0.0" "uuid": "^13.0.0"
},
"devDependencies": {
"@xmpp/debug": "^0.14.0"
} }
} }
} }

@ -1,12 +1,8 @@
{ {
"name": "signpost", "name": "signpost",
"version": "0.2.0", "version": "0.1.0",
"private": true, "private": true,
"workspaces": [ "workspaces": [
"packages/*", "packages/*"
"custom" ]
],
"scripts": {
"custom": "cp custom/package-custom.json custom/package.json"
}
} }

@ -6,7 +6,6 @@ import dotenv from 'dotenv'
* *
* *
*/ */
function config() { function config() {
// getting dir // getting dir

@ -10,17 +10,14 @@ class Action {
/** /**
* *
* @param flow object
* @param data object
* *
*/ */
constructor(flow, data) { constructor(flow, data) {
this.flow = flow this.flow = flow
this.data = data this.data = data
// check if class has a run method if (!this.run()) {
if (typeof this.run !== 'function') { throw new Error('run method needed')
throw new Error('missing run method')
} }
} }
} }

@ -54,6 +54,5 @@ const id = flowStore.create(flow, token)
if (id) { if (id) {
console.log(chalk.green('done')) console.log(chalk.green('done'))
console.log(chalk.blue('uuid: ' + flow.uuid)) console.log(chalk.blue('uuid: ' + flow.uuid))
console.log(chalk.blue('bearer: ' + token)) console.log(chalk.blue('token: ' + token))
console.log(chalk.blue('bearer: ' + encodeURIComponent(token)))
} }

@ -1,27 +0,0 @@
import { input, password } from '@inquirer/prompts'
import { v4 as uuidv4 } from 'uuid'
import path from 'path'
import chalk from 'chalk'
import crypto from 'node:crypto'
import runMigrationSqlite from './../db/migration.js'
import getOrCreateSqlite from './../db/sqlite.js'
import TokenHelper from './../helper/token.js'
// getting flow
import FlowStore from './../store/flow.js'
// get config
import config from './../_config.js'
config()
// getting db and flowStore
const mainDB = getOrCreateSqlite({ 'uri': process.env.APP_SQLITE_URI_MAIN, 'create': true, 'readwrite': true })
const flowStore = new FlowStore(mainDB)
const results = flowStore.find()
for (const result of results) {
console.log(chalk.green(result.uuid + ' / Action: ' + result.action + ' / Schema: ' + result.schema))
}

@ -1,19 +1,10 @@
import { input, password } from '@inquirer/prompts' import { input, password } from '@inquirer/prompts'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid';
import { getOrCreateSqlite } from '@nano/sqlite'
import path from 'path' import path from 'path'
import chalk from 'chalk'
import crypto from 'node:crypto'
import runMigrationSqlite from './../db/migration.js'
import getOrCreateSqlite from './../db/sqlite.js'
// getting flow
import FlowStore from './../store/flow.js' import FlowStore from './../store/flow.js'
// get config
import config from './../_config.js'
config()
// getting db and flowStore // getting db and flowStore
const mainDB = getOrCreateSqlite({ 'uri': process.env.APP_SQLITE_URI_MAIN, 'create': true, 'readwrite': true }) const mainDB = getOrCreateSqlite({ 'uri': process.env.APP_SQLITE_URI_MAIN, 'create': true, 'readwrite': true })
const flowStore = new FlowStore(mainDB) const flowStore = new FlowStore(mainDB)
@ -30,4 +21,4 @@ const uuid = await input({
} }
}) })
flowStore.removeByUuid(uuid) flowStore.deleteByUuid(uuid)

@ -1,44 +0,0 @@
import DOMPurify from 'isomorphic-dompurify'
import TokenHelper from './../helper/token.js'
import logger from './../helper/logger.js'
/**
* handle token
*
* @author Björn Hase <me@herr-hase.wtf>
* @license hhttps://www.gnu.org/licenses/gpl-3.0.en.html GPL-3
* @link https://git.node001.net/HerrHase/signpost.git
*
*/
async function bearerHandler(request, response) {
if (!request.headers.authorization) {
return response
.code(403)
.send()
}
let token = DOMPurify.sanitize(request.headers.authorization)
token = token.match(/^Bearer ([A-Za-z0-9._~+/-]+=*)$/)
// check if token exists
if (!token || !token[1]) {
logger(response.locals.flow.uuid).error('token not found in header')
return response
.code(403)
.send()
}
// check if token is same as for the flow
if (!TokenHelper.equal(token[1], response.locals.flow.hash)) {
logger(response.locals.flow.uuid).error('token not equal with hash from flow')
return response
.code(403)
.send()
}
}
export default bearerHandler

@ -12,11 +12,27 @@ import logger from './../helper/logger.js'
*/ */
async function tokenHandler(request, response) { async function tokenHandler(request, response) {
if (!request.headers.authorization) {
return response
.code(403)
.send()
}
let token = DOMPurify.sanitize(request.params.token) let token = DOMPurify.sanitize(request.headers.authorization)
token = token.match(/^Bearer ([A-Za-z0-9._~+/-]+=*)$/)
// check if token exists
if (!token[1]) {
logger(response.locals.flow.uuid).error('token not found in header')
return response
.code(403)
.send()
}
// check if token is same as for the flow // check if token is same as for the flow
if (!TokenHelper.equal(token, response.locals.flow.hash)) { if (!TokenHelper.equal(token[1], response.locals.flow.hash)) {
logger(response.locals.flow.uuid).error('token not equal with hash from flow') logger(response.locals.flow.uuid).error('token not equal with hash from flow')
return response return response

@ -11,7 +11,7 @@ import path from 'path'
*/ */
function resolveActionClass(className) { function resolveActionClass(className) {
let classPath = path.join(process.env.APP_BASE_DIR, 'custom/actions/' + className + '.js') let classPath = path.join(process.env.APP_BASE_DIR, 'resources/actions/' + className + '.js')
let result = undefined let result = undefined
if (fs.existsSync(classPath)) { if (fs.existsSync(classPath)) {
@ -34,7 +34,7 @@ function resolveActionClass(className) {
*/ */
function resolveSchema(schemaName) { function resolveSchema(schemaName) {
let schemaPath = path.join(process.env.APP_BASE_DIR, 'custom/schemas/' + schemaName + '.json') let schemaPath = path.join(process.env.APP_BASE_DIR, 'resources/schemas/' + schemaName + '.json')
let result = undefined let result = undefined
if (fs.existsSync(schemaPath)) { if (fs.existsSync(schemaPath)) {

@ -20,7 +20,7 @@ const TokenHelper = {
*/ */
create(token) { create(token) {
const hmac = createHmac(process.env.APP_HASH_TYPE, process.env.APP_SALT) const hmac = createHmac(process.env.APP_HASH_TYPE, process.env.APP_SALT)
const buffer = new Buffer.from(token) const buffer = new Buffer(token)
return hmac.update(buffer).digest('base64') return hmac.update(buffer).digest('base64')
}, },

@ -1,5 +1,4 @@
import tokenHandler from './../../handler/token.js' import tokenHandler from './../../handler/token.js'
import bearerHandler from './../../handler/bearer.js'
import flowHandler from './../../handler/flow.js' import flowHandler from './../../handler/flow.js'
import logger from './../../helper/logger.js' import logger from './../../helper/logger.js'
@ -15,6 +14,7 @@ import logger from './../../helper/logger.js'
export default async function(fastify, options) { export default async function(fastify, options) {
fastify.addHook('preHandler', flowHandler) fastify.addHook('preHandler', flowHandler)
fastify.addHook('preHandler', tokenHandler)
/** /**
* *
@ -23,37 +23,7 @@ export default async function(fastify, options) {
* @param {object} response * @param {object} response
* *
*/ */
fastify.post('/:uuid(^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$)', { preHandler: [ bearerHandler ] }, async function (request, response) { fastify.post('/:uuid(^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$)', async function (request, response) {
if (response.locals.schema && !request.validateInput(request.body, response.locals.schema)) {
return response
.code(400)
.send()
}
// running actions
const action = new response.locals.action.default(response.locals.flow, request.body)
response
.code(204)
.send()
// try run from action
try {
await action.run()
} catch(error) {
logger(response.locals.flow.uuid).error('webhook / run / ' + error)
}
})
/**
*
*
* @param {object} request
* @param {object} response
*
*/
fastify.post('/:uuid(^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$)/:token(^[A-Za-z0-9+\/]+={0,2}$)', { preHandler: [ tokenHandler ] }, async function (request, response) {
if (response.locals.schema && !request.validateInput(request.body, response.locals.schema)) { if (response.locals.schema && !request.validateInput(request.body, response.locals.schema)) {
return response return response

@ -1,11 +1,12 @@
{ {
"private": true, "private": true,
"name": "server", "name": "server",
"version": "0.2.0", "version": "0.1.0",
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@fastify/rate-limit": "^10.3.0", "@fastify/rate-limit": "^10.3.0",
"@inquirer/prompts": "^8.3.0", "@inquirer/prompts": "^8.3.0",
"@xmpp/client": "^0.14.0",
"better-sqlite3": "^12.6.2", "better-sqlite3": "^12.6.2",
"chalk": "^5.6.2", "chalk": "^5.6.2",
"dayjs": "^1.11.19", "dayjs": "^1.11.19",
@ -16,11 +17,12 @@
"pino-pretty": "^13.1.3", "pino-pretty": "^13.1.3",
"uuid": "^13.0.0" "uuid": "^13.0.0"
}, },
"devDependencies": {
"@xmpp/debug": "^0.14.0"
},
"scripts": { "scripts": {
"start": "node index.js", "start": "node index.js",
"migrate": "node cli/migrate.js", "migrate": "node cli/migrate.js",
"addFlow": "node cli/addFlow.js", "addFlow": "node cli/addFlow.js"
"removeFlow": "node cli/removeFlow.js",
"indexFlow": "node cli/indexFlow.js"
} }
} }

@ -16,10 +16,10 @@ class FlowStore extends Store {
} }
/** /**
* create hash with current date *
* *
*/ */
create(data) { create(data, hash) {
data.date_created_at = dayjs().toISOString() data.date_created_at = dayjs().toISOString()
return super.create(data) return super.create(data)
} }
@ -32,23 +32,6 @@ class FlowStore extends Store {
return this._db.prepare('SELECT * FROM ' + this._tableName + ' WHERE uuid = ?') return this._db.prepare('SELECT * FROM ' + this._tableName + ' WHERE uuid = ?')
.get(uuid) .get(uuid)
} }
/**
*
*
*/
removeByUuid(uuid) {
return this._db.prepare('DELETE FROM ' + this._tableName + ' WHERE uuid = ?')
.run(uuid)
}
/**
*
*/
find() {
return this._db.prepare('SELECT * FROM ' + this._tableName)
.all()
}
} }
export default FlowStore export default FlowStore

@ -35,7 +35,7 @@ class Store {
* *
*/ */
remove(id) { remove(id) {
return this._db this.db
.prepare('DELETE FROM ' + this._tableName + ' WHERE id = ?') .prepare('DELETE FROM ' + this._tableName + ' WHERE id = ?')
.run(id) .run(id)
} }

Loading…
Cancel
Save