change structure, adding cli, adding alternative webhook

main
HerrHase 1 week ago
parent 664ffa6134
commit d199343535

7
.gitignore vendored

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

@ -22,5 +22,18 @@ curl -X POST https://<domain>/api/flow/v1/<uuid>
-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/

@ -0,0 +1,35 @@
import Action from './../../packages/server/actions/action.js'
import XmppHelper from './../helpers/Xmpp.js'
/**
* Getting Http State
*
* @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 Pingdom extends Action {
/**
*
*
*/
async run() {
const xmppHelper = new XmppHelper(
process.env.XMPP_SERVICE,
process.env.XMPP_DOMAIN,
process.env.XMPP_USERNAME,
process.env.XMPP_PASSWORD
)
const message = this.data.check_params.full_url + ' / ' + this.data.current_state
await xmppHelper.sendToRoom(this.flow, process.env.XMPP_ROOM, message)
}
}
export default Pingdom

@ -0,0 +1,32 @@
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

@ -0,0 +1,80 @@
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

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

@ -0,0 +1,11 @@
{
"private": true,
"name": "custom",
"type": "module",
"dependencies": {
"@xmpp/client": "^0.14.0"
},
"devDependencies": {
"@xmpp/debug": "^0.14.0"
}
}

@ -0,0 +1,32 @@
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

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

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

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

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

@ -0,0 +1,27 @@
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,10 +1,19 @@
import { input, password } from '@inquirer/prompts'
import { v4 as uuidv4 } from 'uuid';
import { getOrCreateSqlite } from '@nano/sqlite'
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'
// 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)
@ -21,4 +30,4 @@ const uuid = await input({
}
})
flowStore.deleteByUuid(uuid)
flowStore.removeByUuid(uuid)

@ -0,0 +1,44 @@
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

@ -13,26 +13,10 @@ import logger from './../helper/logger.js'
async function tokenHandler(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[1]) {
logger(response.locals.flow.uuid).error('token not found in header')
return response
.code(403)
.send()
}
let token = DOMPurify.sanitize(request.params.token)
// check if token is same as for the flow
if (!TokenHelper.equal(token[1], response.locals.flow.hash)) {
if (!TokenHelper.equal(token, response.locals.flow.hash)) {
logger(response.locals.flow.uuid).error('token not equal with hash from flow')
return response

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

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

@ -1,4 +1,5 @@
import tokenHandler from './../../handler/token.js'
import bearerHandler from './../../handler/bearer.js'
import flowHandler from './../../handler/flow.js'
import logger from './../../helper/logger.js'
@ -14,7 +15,6 @@ import logger from './../../helper/logger.js'
export default async function(fastify, options) {
fastify.addHook('preHandler', flowHandler)
fastify.addHook('preHandler', tokenHandler)
/**
*
@ -23,7 +23,37 @@ export default async function(fastify, options) {
* @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}$)', 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}$)', { preHandler: [ bearerHandler ] }, 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)) {
return response

@ -1,12 +1,11 @@
{
"private": true,
"name": "server",
"version": "0.1.0",
"version": "0.2.0",
"type": "module",
"dependencies": {
"@fastify/rate-limit": "^10.3.0",
"@inquirer/prompts": "^8.3.0",
"@xmpp/client": "^0.14.0",
"better-sqlite3": "^12.6.2",
"chalk": "^5.6.2",
"dayjs": "^1.11.19",
@ -17,12 +16,11 @@
"pino-pretty": "^13.1.3",
"uuid": "^13.0.0"
},
"devDependencies": {
"@xmpp/debug": "^0.14.0"
},
"scripts": {
"start": "node index.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, hash) {
create(data) {
data.date_created_at = dayjs().toISOString()
return super.create(data)
}
@ -32,6 +32,23 @@ class FlowStore extends Store {
return this._db.prepare('SELECT * FROM ' + this._tableName + ' WHERE 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

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

Loading…
Cancel
Save