diff --git a/.github/workflows/validation.yml b/.github/workflows/validation.yml new file mode 100644 index 000000000..3e21f91ea --- /dev/null +++ b/.github/workflows/validation.yml @@ -0,0 +1,38 @@ +name: Validation +on: [pull_request] + +jobs: + validation: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Get changed files + id: changed-files + uses: tj-actions/changed-files@v23.1 + - name: Install modules + run: yarn + - name: Run tests + id: tests + run: yarn test + env: + FILES: ${{ steps.changed-files.outputs.all_changed_files }} + actions_path: ${{ github.workspace }} + email_api_key: ${{ secrets.EMAIL_KEY }} + - name: Post message + if: always() + uses: mshick/add-pr-comment@v1 + with: + message: | + **Validation Results** + - Information Validation + ${{steps.tests.outputs.infoMessage}} + ${{steps.tests.outputs.infoReason}} + - DNS Record Validation + ${{steps.tests.outputs.recordMessage}} + + repo-token: ${{ secrets.GITHUB_TOKEN }} + repo-token-user-login: 'is-a-good.dev (Validation) [bot]' # The user.login for temporary GitHub tokens + allow-repeats: false # This is the default + diff --git a/auth.json b/auth.json new file mode 100644 index 000000000..a4006f9df --- /dev/null +++ b/auth.json @@ -0,0 +1,14 @@ +{ + "note": "is-a-good.dev Dashboard", + + "owner": { + "username": "@is-a-good-dev/dashboard-dev-team", + "email": "will@is-a-good.dev" + }, + + "target": { + "CNAME": "is-a-good-dev.github.io" + }, + + "proxied": false + } diff --git a/package.json b/package.json new file mode 100644 index 000000000..e04ff2dac --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "jest", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "jest --verbose" + }, + "keywords": [], + "author": "Tweak4141", + "license": "MIT", + "devDependencies": { + "jest": "28.1.1" + }, + "dependencies": { + "node-fetch": "2.6.7", + "jest": "28.1.1", + "@actions/core": "1.9.0" + } +} diff --git a/tests/json.test.js b/tests/json.test.js new file mode 100644 index 000000000..e4f1942ea --- /dev/null +++ b/tests/json.test.js @@ -0,0 +1,20 @@ +const core = require('@actions/core'); +const getJSON = require('../utils/getJSON.js'); +const checkInfo = require('../utils/checkInfo.js'); +const checkRecords = require('../utils/checkRecords.js'); +const data = getJSON(process.env.FILES); +core.setOutput('infoMessage', "Could not validate info."); +core.setOutput('recordMessage', "Could not validate records."); +test('check if json file has required info', async () => { + const passed = await checkInfo(data); + let infoMessage = passed === true ? "Valid Info Provided." : passed === "unknown" ? "Error Verifying Email.\nA maintainer will have to manually verify your email.\nReason:" : "Invalid Info Provided.\nPlease check your provided info.\nReason:"; + core.setOutput('infoMessage', infoMessage); + expect(passed).toBeTruthy(); +}, 15000); + +test('check if json file follows format', () => { + const passed = checkRecords(data); + let recordMessage = passed === true ? "Valid Records Provided." : "Invalid Records Provided.\nPlease check your provided records.\nThey should only be of type `CNAME` or `A`, and should follow their respective formats."; + core.setOutput('recordMessage', recordMessage); + expect(passed).toBe(true); +}); diff --git a/utils/checkInfo.js b/utils/checkInfo.js new file mode 100644 index 000000000..d66bbed21 --- /dev/null +++ b/utils/checkInfo.js @@ -0,0 +1,26 @@ +const fetch = require('node-fetch'); +const core = require('@actions/core'); + +async function checkEmail(email) { + console.log(`Checking: ${email}`) + const url = `https://email-checker.p.rapidapi.com/verify/v1?email=${encodeURIComponent(email)}`; + const options = { + method: 'GET', + headers: { + 'X-RapidAPI-Key': process.env.email_api_key, + 'X-RapidAPI-Host': 'email-checker.p.rapidapi.com' + } + }; + const res = await fetch(url, options).then(res => res.json()); + console.log(res); + core.setOutput('infoReason', res.reason); + if (res.status == "valid" && res.disposable != true) return true; + if (res.status == "unknown" && res.disposable != true) return "unknown"; + return false; +} + +async function checkInfo(data) { + return await checkEmail(data.owner.email); +} + +module.exports = checkInfo; diff --git a/utils/checkRecords.js b/utils/checkRecords.js new file mode 100644 index 000000000..41850b6aa --- /dev/null +++ b/utils/checkRecords.js @@ -0,0 +1,12 @@ +const { checkIfValidIP, checkIfValidFQDN } = require('./utils.js'); +function checkRecords(data) { + const recordType = Object.keys(data.target)[0] + if (recordType.toLowerCase() === "a") { + return checkIfValidIP(data.target[recordType]) + } + if (recordType.toLowerCase() === "cname") { + return checkIfValidFQDN(data.target[recordType]) + } + return false; +} +module.exports = checkRecords; diff --git a/utils/getJSON.js b/utils/getJSON.js new file mode 100644 index 000000000..7a763037e --- /dev/null +++ b/utils/getJSON.js @@ -0,0 +1,21 @@ +const fs = require('fs'); +function getFileExtension(filename) { + return (/[.]/.exec(filename)) ? /[^.]+$/.exec(filename) : undefined; +} +function getJSON(file) { + const path = `${process.env.actions_path}/${file}`; //json file path + if (!getFileExtension(file) | getFileExtension(file) != "json") return false; //if no file extension, or file extension is not json, return. + try { + if (fs.existsSync(path)) { //check if file exists in domain directory + //it exists + const rawdata = fs.readFileSync(path); //read the file + const data = JSON.parse(rawdata); //parse it + return data; //return true or false, depending if tests pass or fail. + }; + return false; //it doesn't exist + } catch(err) { + console.error(err); + }; + return false; +}; +module.exports = getJSON; diff --git a/utils/utils.js b/utils/utils.js new file mode 100644 index 000000000..30a770bd9 --- /dev/null +++ b/utils/utils.js @@ -0,0 +1,14 @@ +module.exports.checkIfValidIP = function(str) { + // Regular expression to check if string is a IP address + const regexExp = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/gi + + return regexExp.test(str); +}; + +module.exports.checkIfValidFQDN = function(str) { + // Regular expression to check if string is a FQDN + const regexExp = /^(?!:\/\/)([a-zA-Z0-9-_]+\.)?[a-zA-Z0-9][a-zA-Z0-9-]+\.[a-zA-Z]{2,63}?$/gi + + return regexExp.test(str); +}; +