diff --git a/.gitignore b/.gitignore index 7aba794..8834723 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,5 @@ typings/ # next.js build output .next + +built/ diff --git a/app.js b/app.js deleted file mode 100644 index 9efe3cb..0000000 --- a/app.js +++ /dev/null @@ -1,66 +0,0 @@ -const express = require('express'); -const path = require('path'); -const cookieParser = require('cookie-parser'); -const logger = require('morgan'); -const helmet = require('helmet'); -const rateLimit = require('express-rate-limit'); -const compression = require('compression'); -const rfs = require('rotating-file-stream'); -require('dotenv').config(); -const bodyParser = require('body-parser'); -const { VerifySignature } = require('./routes/signatureVerify'); - -const app = express(); - -function rawBodySaver(req, res, buf, encoding) { - if (buf && buf.length) { - req.rawBody = buf.toString(encoding || 'utf8'); - } -} - -const limiter = rateLimit({ - windowMs: 60 * 1000 * process.env.limit_minutes || 120000, // 2 minutes - max: process.env.limit_max_requests || 100, // limit each IP to 100 requests per windowMs -// keyGenerator(req) { // enable if using it behind cloudflare -// return req.headers['cf-connecting-ip']; -// }, -}); - -// create a rotating write stream -const accessLogStream = rfs.createStream('access.log', { - interval: '1d', // rotate daily - path: path.join(process.cwd(), 'logs'), -}); -const shouldCompress = (req, res) => { - if (req.headers['x-no-compression']) { - return false; // don't compress responses with this request header - } - return compression.filter(req, res); // fallback to standard filter function -}; - -app.use(logger('combined', { stream: accessLogStream })); -app.use(helmet()); -app.use(limiter); - -app.disable('x-powered-by'); -app.use(compression({ filter: shouldCompress })); -app.use(cookieParser()); - -app.use(bodyParser.json({ verify: rawBodySaver })); -app.use(bodyParser.raw({ verify: rawBodySaver, type: () => true })); -app.use(bodyParser.urlencoded({ extended: true, verify: rawBodySaver })); -// app.use(express.static(path.join(__dirname, 'public'))); - -const { issuesRoutes, pullRoutes, repoRoutes } = require('./routes'); - -app.use('/api/issues', VerifySignature, issuesRoutes); -app.use('/api/pull', VerifySignature, pullRoutes); -app.use('/api/repo', VerifySignature, repoRoutes); - -// catch 404 and forward to error handler -app.use((req, res, next) => { - // next(createError(404)); - res.status(404).json({ status: 404, message: 'Not Found' }); -}); - -module.exports = app; diff --git a/app.ts b/app.ts new file mode 100644 index 0000000..dc841e1 --- /dev/null +++ b/app.ts @@ -0,0 +1,79 @@ +import express, { Request, Response, NextFunction } from 'express'; +import path from 'path'; +import cookieParser from 'cookie-parser'; +import logger from 'morgan'; +import helmet from 'helmet'; +import rateLimit from 'express-rate-limit'; +import compression from 'compression'; +import {createStream} from 'rotating-file-stream'; + +import bodyParser from 'body-parser'; +import dotenv from 'dotenv'; +dotenv.config(); +import { VerifySignature } from './routes/signatureVerify'; + + +const app = express(); + +// Extend the Request interface to include rawBody property +declare global { + namespace Express { + interface Request { + rawBody?: string; + } + } +} + +function rawBodySaver(req: Request, res: Response, buf: Buffer, encoding: BufferEncoding) { + if (buf && buf.length) { + req.rawBody = buf.toString(encoding || 'utf8'); + } +} + +const limiter = rateLimit({ + windowMs: 60 * 1000 * Number(process.env.limit_minutes) || 120000, // 2 minutes + max: Number(process.env.limit_max_requests) || 100, // limit each IP to 100 requests per windowMs + // keyGenerator(req) { // enable if using it behind cloudflare + // return req.headers['cf-connecting-ip']; + // }, +}); + +// create a rotating write stream +const accessLogStream = createStream('access.log', { + interval: '1d', // rotate daily + path: path.join(process.cwd(), 'logs'), +}); + +const shouldCompress = (req: Request, res: Response): boolean => { + if (req.headers['x-no-compression']) { + return false; // don't compress responses with this request header + } + return compression.filter(req, res); // fallback to standard filter function +}; + +app.use(logger('combined', { stream: accessLogStream })); +app.use(helmet()); +app.use(limiter); + +app.disable('x-powered-by'); +app.use(compression({ filter: shouldCompress })); +app.use(cookieParser()); + +app.use(bodyParser.json({ verify: rawBodySaver })); +app.use(bodyParser.raw({ verify: rawBodySaver, type: () => true })); +app.use(bodyParser.urlencoded({ extended: true, verify: rawBodySaver })); +// app.use(express.static(path.join(__dirname, 'public'))); + +import { issuesRoutes, pullRoutes, repoRoutes } from './routes'; + +app.use('/api/issues', VerifySignature, issuesRoutes); +app.use('/api/pull', VerifySignature, pullRoutes); +app.use('/api/repo', VerifySignature, repoRoutes); + +// catch 404 and forward to error handler +app.use((req: Request, res: Response, next: NextFunction) => { + // next(createError(404)); + res.status(404).json({ status: 404, message: 'Not Found' }); +}); + +export default app; diff --git a/bin/www b/bin/www.ts similarity index 76% rename from bin/www rename to bin/www.ts index 35c2214..d0a92a4 100755 --- a/bin/www +++ b/bin/www.ts @@ -3,25 +3,24 @@ /** * Module dependencies. */ -const debug = require('debug')('anycicd:server'); -const http = require('http'); -const app = require('../app'); -const pack = require('../package.json'); -/** - * Get port from environment and store in Express. - */ +import debug from 'debug'; +import http from 'http'; +import app from '../app'; +import pack from '../package.json'; +import dotenv from 'dotenv'; +dotenv.config(); + +const logger = debug('anycicd:server'); /** * Create HTTP server. */ - const server = http.createServer(app); /** * Normalize a port into a number, string, or false. */ - -function normalizePort(val) { +function normalizePort(val: string): number | string | boolean { const port = parseInt(val, 10); if (isNaN(port)) { @@ -40,8 +39,7 @@ function normalizePort(val) { /** * Event listener for HTTP server "error" event. */ - -function onError(error) { +function onError(error: NodeJS.ErrnoException): void { if (error.syscall !== 'listen') { throw error; } @@ -68,22 +66,21 @@ function onError(error) { /** * Event listener for HTTP server "listening" event. */ - -function onListening() { +function onListening(): void { const addr = server.address(); const bind = typeof addr === 'string' ? `pipe ${addr}` - : `port ${addr.port}`; + : `port ${addr?.port}`; console.log(` AnyCiCd v${pack.version} `); console.log(`> Listening on ${bind}`); } const port = normalizePort(process.env.PORT || '3000'); app.set('port', port); + /** * Listen on provided port, on all network interfaces. */ - server.listen(port); server.on('error', onError); -server.on('listening', onListening); +server.on('listening', onListening); \ No newline at end of file diff --git a/buid.sh b/build.sh similarity index 62% rename from buid.sh rename to build.sh index 40de879..e8060b0 100755 --- a/buid.sh +++ b/build.sh @@ -1,18 +1,23 @@ #!/bin/bash -if [ -d "dist" ] ; then - rm -r -- dist +if [ -d "built" ] ; then + rm -r -- built fi PACKAGE_VERSION=$( awk -F'"' '/"version": ".+"/{ print $4; exit; }' package.json) echo "Building anyCiCd-$PACKAGE_VERSION" function pack(){ - cd dist + if [ ! -d "built" ] ; then + echo "build dir not found , abort packing.." + return + fi + cd built for file in * ; do mkdir "dir_$file" mv "$file" "dir_$file/" cp ../sample.env "dir_$file" + mkdir "dir_$file/exec" mv "dir_$file" "$file" zip -9 -r "$file.zip" "$file" echo $file @@ -20,11 +25,11 @@ function pack(){ } function generic(){ - npx pkg --config package.json -o "./dist/anyCiCd-$PACKAGE_VERSION" ./bin/www + npx pkg --config dist/package.json -o "./built/anyCiCd-generic-$PACKAGE_VERSION" ./dist/bin/www.js pack } function rpi(){ - npx pkg --config rpi_build_conf.json -o "./dist/anyCiCd-$PACKAGE_VERSION" ./bin/www + npx pkg --config rpi_build_conf.json -o "./built/anyCiCd-rpi-$PACKAGE_VERSION" ./dist/bin/www.js pack } diff --git a/package.json b/package.json index 46cc3f6..dd39157 100644 --- a/package.json +++ b/package.json @@ -1,27 +1,27 @@ { "name": "anycicd", - "version": "1.0.1", + "version": "2.0.0", "private": false, - "bin": "bin/www", + "bin": "bin/www.ts", "scripts": { - "start": "node ./bin/www", + "start": "node ./bin/www.ts", "dev": " nodemon ", - "clean": "rm -r -- dist", - "build": "./build.sh generic", + "clean": "rm -r -- dist built", + "compile": "tsc", + "build": "yarn run compile;yarn run build:generic", + "build:generic": "./build.sh generic", "build:armv6": "./build.sh rpi" }, "pkg": { "scripts": "./*.js", "assets": [ - "public/**/*", "logs/*", "exec/*" ], "targets": [ - "node16-x64-linux", - "node16-x64-windows" - ], - "outputPath": "dist" + "latest-x64-linux", + "latest-x64-windows" + ] }, "dependencies": { "body-parser": "^1.20.2", @@ -37,10 +37,18 @@ "shelljs": "^0.8.5" }, "devDependencies": { + "@types/compression": "^1.7.5", + "@types/cookie-parser": "^1.4.6", + "@types/debug": "^4.1.12", + "@types/express": "^4.17.21", + "@types/morgan": "^1.9.9", + "@types/node": "^20.11.19", + "@types/shelljs": "^0.8.15", "eslint": "^8.56.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-plugin-import": "^2.29.1", "nodemon": "^3.0.3", - "pkg": "^5.8.1" + "pkg": "^5.8.1", + "typescript": "^5.3.3" } } diff --git a/public/index.html b/public/index.html deleted file mode 100644 index ab1ad8a..0000000 --- a/public/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - Express - - - - -

Express

-

Welcome to Express

- - - diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css deleted file mode 100644 index 9453385..0000000 --- a/public/stylesheets/style.css +++ /dev/null @@ -1,8 +0,0 @@ -body { - padding: 50px; - font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; -} - -a { - color: #00B7FF; -} diff --git a/routes/index.js b/routes/index.js deleted file mode 100644 index bed6b3b..0000000 --- a/routes/index.js +++ /dev/null @@ -1,9 +0,0 @@ -const issuesRoutes = require('./issue'); -const pullRoutes = require('./pull'); -const repoRoutes = require('./repository'); - -module.exports = { - issuesRoutes, - pullRoutes, - repoRoutes, -}; diff --git a/routes/index.ts b/routes/index.ts new file mode 100644 index 0000000..119f0d5 --- /dev/null +++ b/routes/index.ts @@ -0,0 +1,9 @@ +import issuesRoutes from './issue'; +import pullRoutes from './pull'; +import repoRoutes from './repository'; + +export { + issuesRoutes, + pullRoutes, + repoRoutes, +}; diff --git a/routes/issue.js b/routes/issue.ts similarity index 62% rename from routes/issue.js rename to routes/issue.ts index d89a710..389f41a 100644 --- a/routes/issue.js +++ b/routes/issue.ts @@ -1,27 +1,27 @@ -const express = require('express'); -const shell = require('shelljs'); +import express, { Request, Response, NextFunction } from 'express'; +import shell from 'shelljs'; const router = express.Router(); -const openExec = process.env.issues_open || null; -const closeExec = process.env.issues_close || null; -const reopenExec = process.env.issues_reopen || null; -const updateExec = process.env.issues_update || null; +const openExec: string | null = process.env.issues_open || null; +const closeExec: string | null = process.env.issues_close || null; +const reopenExec: string | null = process.env.issues_reopen || null; +const updateExec: string | null = process.env.issues_update || null; -const labelClearExec = process.env.issues_label_clear || null; -const labelUpdateExec = process.env.issues_label_update || null; +const labelClearExec: string | null = process.env.issues_label_clear || null; +const labelUpdateExec: string | null = process.env.issues_label_update || null; -const commentCreateExec = process.env.issues_comment_create || null; -const commentUpdateExec = process.env.issues_comment_update || null; -const commentDeleteExec = process.env.issues_comment_delete || null; +const commentCreateExec: string | null = process.env.issues_comment_create || null; +const commentUpdateExec: string | null = process.env.issues_comment_update || null; +const commentDeleteExec: string | null = process.env.issues_comment_delete || null; -const assignExec = process.env.issues_assign || null; -const unassignExec = process.env.issues_unassign || null; +const assignExec: string | null = process.env.issues_assign || null; +const unassignExec: string | null = process.env.issues_unassign || null; -const milistoneExec = process.env.issues_milistone || null; -const demilistoneExec = process.env.issues_demilistone || null; +const milestoneExec: string | null = process.env.issues_milestone || null; +const demilestoneExec: string | null = process.env.issues_demilestone || null; -router.post('/open', (req, res, next) => { +router.post('/open', (req: Request, res: Response, next: NextFunction) => { if (!openExec) { return res.status(500).json({ status: 'Failed', @@ -29,7 +29,7 @@ router.post('/open', (req, res, next) => { }); } if (req.body.action !== 'opened') return res.end(); - const execReturnCode = shell.exec(`${openExec} '${JSON.stringify(req.body)}'`).code; + const execReturnCode: number = shell.exec(`${openExec} '${JSON.stringify(req.body)}'`).code; if (execReturnCode !== 0) { return res.status(500).json({ status: 'Failed', @@ -41,7 +41,7 @@ router.post('/open', (req, res, next) => { }); }); -router.post('/close', (req, res, next) => { +router.post('/close', (req: Request, res: Response, next: NextFunction) => { if (!closeExec) { return res.status(500).json({ status: 'Failed', @@ -49,7 +49,7 @@ router.post('/close', (req, res, next) => { }); } if (req.body.action !== 'closed') return res.end(); - const execReturnCode = shell.exec(`${closeExec} '${JSON.stringify(req.body)}'`).code; + const execReturnCode: number = shell.exec(`${closeExec} '${JSON.stringify(req.body)}'`).code; if (execReturnCode !== 0) { return res.status(500).json({ status: 'Failed', @@ -61,7 +61,7 @@ router.post('/close', (req, res, next) => { }); }); -router.post('/reopen', (req, res, next) => { +router.post('/reopen', (req: Request, res: Response, next: NextFunction) => { if (!reopenExec) { return res.status(500).json({ status: 'Failed', @@ -69,7 +69,7 @@ router.post('/reopen', (req, res, next) => { }); } if (req.body.action !== 'reopened') return res.end(); - const execReturnCode = shell.exec(`${reopenExec} '${JSON.stringify(req.body)}'`).code; + const execReturnCode: number = shell.exec(`${reopenExec} '${JSON.stringify(req.body)}'`).code; if (execReturnCode !== 0) { return res.status(500).json({ status: 'Failed', @@ -81,7 +81,7 @@ router.post('/reopen', (req, res, next) => { }); }); -router.post('/update', (req, res, next) => { +router.post('/update', (req: Request, res: Response, next: NextFunction) => { if (!updateExec) { return res.status(500).json({ status: 'Failed', @@ -89,7 +89,7 @@ router.post('/update', (req, res, next) => { }); } if (req.body.action !== 'edited') return res.end(); - const execReturnCode = shell.exec(`${updateExec} '${JSON.stringify(req.body)}'`).code; + const execReturnCode: number = shell.exec(`${updateExec} '${JSON.stringify(req.body)}'`).code; if (execReturnCode !== 0) { return res.status(500).json({ status: 'Failed', @@ -101,7 +101,7 @@ router.post('/update', (req, res, next) => { }); }); -router.post('/label/clear', (req, res, next) => { +router.post('/label/clear', (req: Request, res: Response, next: NextFunction) => { if (!labelClearExec) { return res.status(500).json({ status: 'Failed', @@ -109,7 +109,7 @@ router.post('/label/clear', (req, res, next) => { }); } if (req.body.action !== 'label_cleared') return res.end(); - const execReturnCode = shell.exec(`${labelClearExec} '${JSON.stringify(req.body)}'`).code; + const execReturnCode: number = shell.exec(`${labelClearExec} '${JSON.stringify(req.body)}'`).code; if (execReturnCode !== 0) { return res.status(500).json({ status: 'Failed', @@ -121,7 +121,7 @@ router.post('/label/clear', (req, res, next) => { }); }); -router.post('/label/update', (req, res, next) => { +router.post('/label/update', (req: Request, res: Response, next: NextFunction) => { if (!labelUpdateExec) { return res.status(500).json({ status: 'Failed', @@ -129,7 +129,7 @@ router.post('/label/update', (req, res, next) => { }); } if (req.body.action !== 'label_updated') return res.end(); - const execReturnCode = shell.exec(`${labelUpdateExec} '${JSON.stringify(req.body)}'`).code; + const execReturnCode: number = shell.exec(`${labelUpdateExec} '${JSON.stringify(req.body)}'`).code; if (execReturnCode !== 0) { return res.status(500).json({ status: 'Failed', @@ -141,7 +141,7 @@ router.post('/label/update', (req, res, next) => { }); }); -router.post('/comment/create', (req, res, next) => { +router.post('/comment/create', (req: Request, res: Response, next: NextFunction) => { if (!commentCreateExec) { return res.status(500).json({ status: 'Failed', @@ -149,7 +149,7 @@ router.post('/comment/create', (req, res, next) => { }); } if (req.body.action !== 'created') return res.end(); - const execReturnCode = shell.exec(`${commentCreateExec} '${JSON.stringify(req.body)}'`).code; + const execReturnCode: number = shell.exec(`${commentCreateExec} '${JSON.stringify(req.body)}'`).code; if (execReturnCode !== 0) { return res.status(500).json({ status: 'Failed', @@ -161,7 +161,7 @@ router.post('/comment/create', (req, res, next) => { }); }); -router.post('/comment/update', (req, res, next) => { +router.post('/comment/update', (req: Request, res: Response, next: NextFunction) => { if (!commentUpdateExec) { return res.status(500).json({ status: 'Failed', @@ -169,7 +169,7 @@ router.post('/comment/update', (req, res, next) => { }); } if (req.body.action !== 'edited') return res.end(); - const execReturnCode = shell.exec(`${commentUpdateExec} '${JSON.stringify(req.body)}'`).code; + const execReturnCode: number = shell.exec(`${commentUpdateExec} '${JSON.stringify(req.body)}'`).code; if (execReturnCode !== 0) { return res.status(500).json({ status: 'Failed', @@ -181,7 +181,7 @@ router.post('/comment/update', (req, res, next) => { }); }); -router.post('/comment/delete', (req, res, next) => { +router.post('/comment/delete', (req: Request, res: Response, next: NextFunction) => { if (!commentDeleteExec) { return res.status(500).json({ status: 'Failed', @@ -189,7 +189,7 @@ router.post('/comment/delete', (req, res, next) => { }); } if (req.body.action !== 'deleted') return res.end(); - const execReturnCode = shell.exec(`${commentDeleteExec} '${JSON.stringify(req.body)}'`).code; + const execReturnCode: number = shell.exec(`${commentDeleteExec} '${JSON.stringify(req.body)}'`).code; if (execReturnCode !== 0) { return res.status(500).json({ status: 'Failed', @@ -201,7 +201,7 @@ router.post('/comment/delete', (req, res, next) => { }); }); -router.post('/assign', (req, res, next) => { +router.post('/assign', (req: Request, res: Response, next: NextFunction) => { if (!assignExec) { return res.status(500).json({ status: 'Failed', @@ -209,7 +209,7 @@ router.post('/assign', (req, res, next) => { }); } if (req.body.action !== 'assigned') return res.end(); - const execReturnCode = shell.exec(`${assignExec} '${JSON.stringify(req.body)}'`).code; + const execReturnCode: number = shell.exec(`${assignExec} '${JSON.stringify(req.body)}'`).code; if (execReturnCode !== 0) { return res.status(500).json({ status: 'Failed', @@ -221,7 +221,7 @@ router.post('/assign', (req, res, next) => { }); }); -router.post('/unassign', (req, res, next) => { +router.post('/unassign', (req: Request, res: Response, next: NextFunction) => { if (!unassignExec) { return res.status(500).json({ status: 'Failed', @@ -229,7 +229,7 @@ router.post('/unassign', (req, res, next) => { }); } if (req.body.action !== 'unassigned') return res.end(); - const execReturnCode = shell.exec(`${unassignExec} '${JSON.stringify(req.body)}'`).code; + const execReturnCode: number = shell.exec(`${unassignExec} '${JSON.stringify(req.body)}'`).code; if (execReturnCode !== 0) { return res.status(500).json({ status: 'Failed', @@ -241,15 +241,15 @@ router.post('/unassign', (req, res, next) => { }); }); -router.post('/milistone', (req, res, next) => { - if (!milistoneExec) { +router.post('/milestone', (req: Request, res: Response, next: NextFunction) => { + if (!milestoneExec) { return res.status(500).json({ status: 'Failed', reason: 'Not defined executable script for this event', }); } if (req.body.action !== 'milestoned') return res.end(); - const execReturnCode = shell.exec(`${milistoneExec} '${JSON.stringify(req.body)}'`).code; + const execReturnCode: number = shell.exec(`${milestoneExec} '${JSON.stringify(req.body)}'`).code; if (execReturnCode !== 0) { return res.status(500).json({ status: 'Failed', @@ -261,15 +261,15 @@ router.post('/milistone', (req, res, next) => { }); }); -router.post('/demilistone', (req, res, next) => { - if (!demilistoneExec) { +router.post('/demilestone', (req: Request, res: Response, next: NextFunction) => { + if (!demilestoneExec) { return res.status(500).json({ status: 'Failed', reason: 'Not defined executable script for this event', }); } if (req.body.action !== 'demilestoned') return res.end(); - const execReturnCode = shell.exec(`${demilistoneExec} '${JSON.stringify(req.body)}'`).code; + const execReturnCode: number = shell.exec(`${demilestoneExec} '${JSON.stringify(req.body)}'`).code; if (execReturnCode !== 0) { return res.status(500).json({ status: 'Failed', @@ -281,4 +281,4 @@ router.post('/demilistone', (req, res, next) => { }); }); -module.exports = router; +export default router; diff --git a/routes/pull.js b/routes/pull.js deleted file mode 100644 index 089ba38..0000000 --- a/routes/pull.js +++ /dev/null @@ -1,18 +0,0 @@ -const express = require('express'); - -const router = express.Router(); - -/* GET home page. */ -router.get('/', (req, res, next) => { - res.json({ test: 'ok' }); -}); - -router.post('/', (req, res, next) => { - // console.dir(req.body) - const giteaSignature = req.headers['x-gitea-signature']; - console.dir(giteaSignature); - - res.json({ test: 'ok' }); -}); - -module.exports = router; diff --git a/routes/pull.ts b/routes/pull.ts new file mode 100644 index 0000000..34af8a4 --- /dev/null +++ b/routes/pull.ts @@ -0,0 +1,18 @@ +import express, { Request, Response, NextFunction } from 'express'; + +const router = express.Router(); + +/* GET home page. */ +router.get('/', (req: Request, res: Response, next: NextFunction) => { + res.json({ test: 'ok' }); +}); + +router.post('/', (req: Request, res: Response, next: NextFunction) => { + // console.dir(req.body) + const giteaSignature: string | undefined = req.headers['x-gitea-signature'] as string; + console.dir(giteaSignature); + + res.json({ test: 'ok' }); +}); + +export default router; diff --git a/routes/repository.js b/routes/repository.ts similarity index 59% rename from routes/repository.js rename to routes/repository.ts index ad64956..1d93b1d 100644 --- a/routes/repository.js +++ b/routes/repository.ts @@ -1,31 +1,31 @@ -const express = require('express'); -const shell = require('shelljs'); +import express, { Request, Response, NextFunction } from 'express'; +import shell from 'shelljs'; const router = express.Router(); -const pushExec = process.env.repo_push || null; -const createExec = process.env.repo_create || null; -const deleteExec = process.env.repo_delete || null; +const pushExec: string | null = process.env.repo_push || null; +const createExec: string | null = process.env.repo_create || null; +const deleteExec: string | null = process.env.repo_delete || null; -const branhCreateExec = process.env.repo_branch_create || null; -const branhDeleteExec = process.env.repo_branch_delete || null; -const tagCreateExec = process.env.repo_tag_create || null; -const tagDeleteExec = process.env.repo_tag_delete || null; +const branhCreateExec: string | null = process.env.repo_branch_create || null; +const branhDeleteExec: string | null = process.env.repo_branch_delete || null; +const tagCreateExec: string | null = process.env.repo_tag_create || null; +const tagDeleteExec: string | null = process.env.repo_tag_delete || null; -const forkExec = process.env.repo_fork || null; +const forkExec: string | null = process.env.repo_fork || null; -const releasePublishExec = process.env.repo_release_publish || null; -const releaseUpdateExec = process.env.repo_release_update || null; -const releaseDeleteExec = process.env.repo_release_delete || null; +const releasePublishExec: string | null = process.env.repo_release_publish || null; +const releaseUpdateExec: string | null = process.env.repo_release_update || null; +const releaseDeleteExec: string | null = process.env.repo_release_delete || null; -router.post('/push', (req, res, next) => { +router.post('/push', (req: Request, res: Response, next: NextFunction) => { if (!pushExec) { return res.status(500).json({ status: 'Failed', reason: 'Not defined executable script for this event', }); } - const execReturnCode = shell.exec(`${pushExec} '${JSON.stringify(req.body)}'`).code; + const execReturnCode: number = shell.exec(`${pushExec} '${JSON.stringify(req.body)}'`).code; if (execReturnCode !== 0) { return res.status(500).json({ status: 'Failed', @@ -37,14 +37,14 @@ router.post('/push', (req, res, next) => { }); }); -router.post('/create', (req, res, next) => { +router.post('/create', (req: Request, res: Response, next: NextFunction) => { if (!createExec) { return res.status(500).json({ status: 'Failed', reason: 'Not defined executable script for this event', }); } - const execReturnCode = shell.exec(`${createExec} '${JSON.stringify(req.body)}'`).code; + const execReturnCode: number = shell.exec(`${createExec} '${JSON.stringify(req.body)}'`).code; if (execReturnCode !== 0) { return res.status(500).json({ status: 'Failed', @@ -56,14 +56,14 @@ router.post('/create', (req, res, next) => { }); }); -router.post('/delete', (req, res, next) => { +router.post('/delete', (req: Request, res: Response, next: NextFunction) => { if (!deleteExec) { return res.status(500).json({ status: 'Failed', reason: 'Not defined executable script for this event', }); } - const execReturnCode = shell.exec(`${deleteExec} '${JSON.stringify(req.body)}'`).code; + const execReturnCode: number = shell.exec(`${deleteExec} '${JSON.stringify(req.body)}'`).code; if (execReturnCode !== 0) { return res.status(500).json({ status: 'Failed', @@ -75,14 +75,14 @@ router.post('/delete', (req, res, next) => { }); }); -router.post('/branch/create', (req, res, next) => { +router.post('/branch/create', (req: Request, res: Response, next: NextFunction) => { if (!branhCreateExec) { return res.status(500).json({ status: 'Failed', reason: 'Not defined executable script for this event', }); } - const execReturnCode = shell.exec(`${branhCreateExec} '${JSON.stringify(req.body)}'`).code; + const execReturnCode: number = shell.exec(`${branhCreateExec} '${JSON.stringify(req.body)}'`).code; if (execReturnCode !== 0) { return res.status(500).json({ status: 'Failed', @@ -94,14 +94,14 @@ router.post('/branch/create', (req, res, next) => { }); }); -router.post('/branch/delete', (req, res, next) => { +router.post('/branch/delete', (req: Request, res: Response, next: NextFunction) => { if (!branhDeleteExec) { return res.status(500).json({ status: 'Failed', reason: 'Not defined executable script for this event', }); } - const execReturnCode = shell.exec(`${branhDeleteExec} '${JSON.stringify(req.body)}'`).code; + const execReturnCode: number = shell.exec(`${branhDeleteExec} '${JSON.stringify(req.body)}'`).code; if (execReturnCode !== 0) { return res.status(500).json({ status: 'Failed', @@ -113,14 +113,14 @@ router.post('/branch/delete', (req, res, next) => { }); }); -router.post('/tag/create', (req, res, next) => { +router.post('/tag/create', (req: Request, res: Response, next: NextFunction) => { if (!tagCreateExec) { return res.status(500).json({ status: 'Failed', reason: 'Not defined executable script for this event', }); } - const execReturnCode = shell.exec(`${tagCreateExec} '${JSON.stringify(req.body)}'`).code; + const execReturnCode: number = shell.exec(`${tagCreateExec} '${JSON.stringify(req.body)}'`).code; if (execReturnCode !== 0) { return res.status(500).json({ status: 'Failed', @@ -132,14 +132,14 @@ router.post('/tag/create', (req, res, next) => { }); }); -router.post('/tag/delete', (req, res, next) => { +router.post('/tag/delete', (req: Request, res: Response, next: NextFunction) => { if (!tagDeleteExec) { return res.status(500).json({ status: 'Failed', reason: 'Not defined executable script for this event', }); } - const execReturnCode = shell.exec(`${tagDeleteExec} '${JSON.stringify(req.body)}'`).code; + const execReturnCode: number = shell.exec(`${tagDeleteExec} '${JSON.stringify(req.body)}'`).code; if (execReturnCode !== 0) { return res.status(500).json({ status: 'Failed', @@ -151,14 +151,14 @@ router.post('/tag/delete', (req, res, next) => { }); }); -router.post('/fork', (req, res, next) => { +router.post('/fork', (req: Request, res: Response, next: NextFunction) => { if (!forkExec) { return res.status(500).json({ status: 'Failed', reason: 'Not defined executable script for this event', }); } - const execReturnCode = shell.exec(`${forkExec} '${JSON.stringify(req.body)}'`).code; + const execReturnCode: number = shell.exec(`${forkExec} '${JSON.stringify(req.body)}'`).code; if (execReturnCode !== 0) { return res.status(500).json({ status: 'Failed', @@ -170,14 +170,14 @@ router.post('/fork', (req, res, next) => { }); }); -router.post('/release/publish', (req, res, next) => { +router.post('/release/publish', (req: Request, res: Response, next: NextFunction) => { if (!releasePublishExec) { return res.status(500).json({ status: 'Failed', reason: 'Not defined executable script for this event', }); } - const execReturnCode = shell.exec(`${releasePublishExec} '${JSON.stringify(req.body)}'`).code; + const execReturnCode: number = shell.exec(`${releasePublishExec} '${JSON.stringify(req.body)}'`).code; if (execReturnCode !== 0) { return res.status(500).json({ status: 'Failed', @@ -189,14 +189,14 @@ router.post('/release/publish', (req, res, next) => { }); }); -router.post('/release/update', (req, res, next) => { +router.post('/release/update', (req: Request, res: Response, next: NextFunction) => { if (!releaseUpdateExec) { return res.status(500).json({ status: 'Failed', reason: 'Not defined executable script for this event', }); } - const execReturnCode = shell.exec(`${releaseUpdateExec} '${JSON.stringify(req.body)}'`).code; + const execReturnCode: number = shell.exec(`${releaseUpdateExec} '${JSON.stringify(req.body)}'`).code; if (execReturnCode !== 0) { return res.status(500).json({ status: 'Failed', @@ -208,14 +208,14 @@ router.post('/release/update', (req, res, next) => { }); }); -router.post('/release/delete', (req, res, next) => { +router.post('/release/delete', (req: Request, res: Response, next: NextFunction) => { if (!releaseDeleteExec) { return res.status(500).json({ status: 'Failed', reason: 'Not defined executable script for this event', }); } - const execReturnCode = shell.exec(`${releaseDeleteExec} '${JSON.stringify(req.body)}'`).code; + const execReturnCode: number = shell.exec(`${releaseDeleteExec} '${JSON.stringify(req.body)}'`).code; if (execReturnCode !== 0) { return res.status(500).json({ status: 'Failed', @@ -227,4 +227,4 @@ router.post('/release/delete', (req, res, next) => { }); }); -module.exports = router; +export default router; \ No newline at end of file diff --git a/routes/signatureVerify.js b/routes/signatureVerify.js deleted file mode 100644 index 27cafc9..0000000 --- a/routes/signatureVerify.js +++ /dev/null @@ -1,24 +0,0 @@ -const crypto = require('crypto'); - -const secret = process.env.SECRET; -const sigHeaderName = 'x-gitea-signature'; -const sigHashAlg = 'sha256'; - -function VerifySignature(req, res, next) { - if (!req.body || !req.rawBody) { - res.setHeader('Content-Type', 'application/json'); - return res.status(400).json({ error: 'Request body empty' }); - } - - const headerSignature = Buffer.from(req.get(sigHeaderName) || '', 'utf8'); - const hmac = crypto.createHmac(sigHashAlg, secret); - const digest = Buffer.from(`${hmac.update(req.rawBody).digest('hex')}`, 'utf8'); - - if (headerSignature.length !== digest.length - || !crypto.timingSafeEqual(digest, headerSignature)) { - return res.status(400).json({ error: 'Data validation failed', reason: `Request body digest (${digest}) did not match ${sigHeaderName} (${headerSignature})` }); - } - return next(); -} - -module.exports = { VerifySignature }; diff --git a/routes/signatureVerify.ts b/routes/signatureVerify.ts new file mode 100644 index 0000000..8041742 --- /dev/null +++ b/routes/signatureVerify.ts @@ -0,0 +1,34 @@ +import { Request, Response, NextFunction } from 'express'; +import crypto from 'crypto'; + +const secret: string | undefined = process.env.SECRET; +const sigHeaderName: string = 'x-gitea-signature'; +const sigHashAlg: string = 'sha256'; + + +function VerifySignature(req: Request, res: Response, next: NextFunction): void { + if (!req.body || !req.rawBody) { + res.setHeader('Content-Type', 'application/json'); + res.status(400).json({ error: 'Request body empty' }); + return; + } + const headerSignature: Buffer = Buffer.from(req.get(sigHeaderName) || '', 'utf8'); + if (!secret) { + res.status(500).json({ error: 'Internal Server Error', reason: 'Secret is not defined' }); + return; + } + + const hmac: crypto.Hmac = crypto.createHmac(sigHashAlg, secret); + const digest: Buffer = Buffer.from(`${hmac.update(req.rawBody).digest('hex')}`, 'utf8'); + + if (headerSignature.length !== digest.length || !crypto.timingSafeEqual(digest, headerSignature)) { + res.status(400).json({ + error: 'Data validation failed', + reason: `Request body digest (${digest}) did not match ${sigHeaderName} (${headerSignature})` + }); + return; + } + next(); +} + +export { VerifySignature }; diff --git a/rpi_build_conf.json b/rpi_build_conf.json index 90414da..aab5c7a 100644 --- a/rpi_build_conf.json +++ b/rpi_build_conf.json @@ -1,15 +1,13 @@ { - "bin": "bin/www", "pkg": { "scripts": "./*.js", "assets": [ - "public/**/*", "logs/*", "exec/*" ], "targets": [ - "node16-linux-arm64" + "latest-linux-arm64" ], - "outputPath": "dist" + "outputPath": "built" } } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..0ec5c15 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "outDir": "dist", + "target": "es6", + "module": "commonjs", + "moduleResolution": "node" , + "sourceMap": false, + "removeComments": false, + "esModuleInterop": true , + "strict": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule":true, + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + }, + "include": [ + "./app.ts", + "bin/*.ts", + "exec/**/*.sh", + "routes/**/*.ts" // Adjust the path to match your file structure + ], + "exclude": ["node_modules"] + } + \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 266d734..bffea76 100644 --- a/yarn.lock +++ b/yarn.lock @@ -153,11 +153,149 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@types/body-parser@*": + version "1.19.5" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4" + integrity sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/compression@^1.7.5": + version "1.7.5" + resolved "https://registry.yarnpkg.com/@types/compression/-/compression-1.7.5.tgz#0f80efef6eb031be57b12221c4ba6bc3577808f7" + integrity sha512-AAQvK5pxMpaT+nDvhHrsBhLSYG5yQdtkaJE1WYieSNY2mVFKAgmU4ks65rkZD5oqnGCFLyQpUr1CqI4DmUMyDg== + dependencies: + "@types/express" "*" + +"@types/connect@*": + version "3.4.38" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" + integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== + dependencies: + "@types/node" "*" + +"@types/cookie-parser@^1.4.6": + version "1.4.6" + resolved "https://registry.yarnpkg.com/@types/cookie-parser/-/cookie-parser-1.4.6.tgz#002643c514cccf883a65cbe044dbdc38c0b92ade" + integrity sha512-KoooCrD56qlLskXPLGUiJxOMnv5l/8m7cQD2OxJ73NPMhuSz9PmvwRD6EpjDyKBVrdJDdQ4bQK7JFNHnNmax0w== + dependencies: + "@types/express" "*" + +"@types/debug@^4.1.12": + version "4.1.12" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917" + integrity sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ== + dependencies: + "@types/ms" "*" + +"@types/express-serve-static-core@^4.17.33": + version "4.17.43" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz#10d8444be560cb789c4735aea5eac6e5af45df54" + integrity sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express@*", "@types/express@^4.17.21": + version "4.17.21" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d" + integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.33" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/glob@~7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" + integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA== + dependencies: + "@types/minimatch" "*" + "@types/node" "*" + +"@types/http-errors@*": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f" + integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== + "@types/json5@^0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== +"@types/mime@*": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.4.tgz#2198ac274de6017b44d941e00261d5bc6a0e0a45" + integrity sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw== + +"@types/mime@^1": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" + integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== + +"@types/minimatch@*": + version "5.1.2" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca" + integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== + +"@types/morgan@^1.9.9": + version "1.9.9" + resolved "https://registry.yarnpkg.com/@types/morgan/-/morgan-1.9.9.tgz#d60dec3979e16c203a000159daa07d3fb7270d7f" + integrity sha512-iRYSDKVaC6FkGSpEVVIvrRGw0DfJMiQzIn3qr2G5B3C//AWkulhXgaBd7tS9/J79GWSYMTHGs7PfI5b3Y8m+RQ== + dependencies: + "@types/node" "*" + +"@types/ms@*": + version "0.7.34" + resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.34.tgz#10964ba0dee6ac4cd462e2795b6bebd407303433" + integrity sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g== + +"@types/node@*", "@types/node@^20.11.19": + version "20.11.19" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.19.tgz#b466de054e9cb5b3831bee38938de64ac7f81195" + integrity sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ== + dependencies: + undici-types "~5.26.4" + +"@types/qs@*": + version "6.9.11" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.11.tgz#208d8a30bc507bd82e03ada29e4732ea46a6bbda" + integrity sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ== + +"@types/range-parser@*": + version "1.2.7" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" + integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== + +"@types/send@*": + version "0.17.4" + resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a" + integrity sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/serve-static@*": + version "1.15.5" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.5.tgz#15e67500ec40789a1e8c9defc2d32a896f05b033" + integrity sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ== + dependencies: + "@types/http-errors" "*" + "@types/mime" "*" + "@types/node" "*" + +"@types/shelljs@^0.8.15": + version "0.8.15" + resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.8.15.tgz#22c6ab9dfe05cec57d8e6cb1a95ea173aee9fcac" + integrity sha512-vzmnCHl6hViPu9GNLQJ+DZFd6BQI2DBTUeOvYHqkWQLMfKAAQYMb/xAmZkTogZI/vqXHCWkqDRymDI5p0QTi5Q== + dependencies: + "@types/glob" "~7.2.0" + "@types/node" "*" + "@ungap/structured-clone@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" @@ -2558,6 +2696,11 @@ typed-array-length@^1.0.4: for-each "^0.3.3" is-typed-array "^1.1.9" +typescript@^5.3.3: + version "5.3.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" + integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== + unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" @@ -2573,6 +2716,11 @@ undefsafe@^2.0.5: resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + universalify@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d"