Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: add template schema validation github action #4

Merged
merged 4 commits into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions .github/workflows/validators.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: Templates Validation

on:
pull_request:
types:
- opened
- synchronize

jobs:
install-cache:
runs-on: ubuntu-latest
name: Install & Cache modules
steps:
- uses: actions/checkout@v3
- name: Cache node modules
uses: actions/cache@v3
env:
cache-name: cache-node-modules
with:
path: |
node_modules
key: ${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
- uses: actions/setup-node@v3
with:
node-version: '18.x'
always-auth: true
- name: Install dependencies
if: steps.cache-dependencies.outputs.cache-hit != true
run: npm ci

lint:
needs: install-cache
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Use node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Restore Cached Dependencies
uses: actions/cache@v3
id: cache-dependencies
with:
path: |
node_modules
key: ${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
- name: Lint Repo
run: npm run pretty:check:templates

validate:
needs: lint
name: Validate Schema
runs-on: ubuntu-latest

steps:
- name: Check Out Repository
uses: actions/checkout@v4

- name: Set Up Node.js
uses: actions/setup-node@v3
with:
node-version: 18

- name: Restore Cached Dependencies
uses: actions/cache@v3
id: cache-dependencies
with:
path: |
node_modules
key: ${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-

- name: Validate Templates
run: npm run validate:templates
73 changes: 73 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "opensource-marketplace",
"version": "0.0.1",
"description": "A collection 3rd-party code that's used to manage the open source marketplace contributions.",
"scripts": {
"pretty:check:templates": "prettier --check ./templates",
"validate:templates": "node ./scripts/templates/validate-templates.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/auth0/opensource-marketplace.git"
},
"author": "",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/auth0/opensource-marketplace/issues"
},
"homepage": "https://github.com/auth0/opensource-marketplace#readme",
"devDependencies": {
"js-yaml": "^4.1.0",
"prettier": "^3.0.3",
"zod": "^3.22.4",
"zod-validation-error": "^2.1.0"
}
}
140 changes: 140 additions & 0 deletions scripts/templates/validate-templates.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
const yamlParser = require('js-yaml');
const fs = require('fs');
const path = require('path');
const { z } = require('zod');
const { fromZodError } = require('zod-validation-error');

// Changes here must be reflected here:
// https://github.com/auth0/managed-marketplace/blob/main/prisma/schema.prisma#L198
const IntegrationTrigger = [
'POST_LOGIN',
'CREDENTIALS_EXCHANGE',
'PRE_USER_REGISTRATION',
'POST_USER_REGISTRATION',
'POST_CHANGE_PASSWORD',
'SEND_PHONE_MESSAGE',
'IGA_APPROVAL',
'IGA_CERTIFICATION',
'IGA_FULFILLMENT_ASSIGNMENT',
'IGA_FULFILLMENT_EXECUTION',
'PASSWORD_RESET_POST_CHALLENGE',
];

const UseCase = [
'MULTIFACTOR',
'ACTION_FEATURE',
'ENRICH_PROFILE',
'ACCESS_CONTROL',
];

const configValue = z.object({
label: z.string().min(2),
defaultValue: z.string().min(2),
});
const moduleValue = z.object({
name: z.string().min(2),
version: z.string().min(2),
});

const TemplateSchema = z
.object({
id: z.string().uuid(),
johneke-auth0 marked this conversation as resolved.
Show resolved Hide resolved
name: z.string().min(3),
triggers: z.array(z.enum(IntegrationTrigger)),
useCases: z.array(z.enum(UseCase)),
public: z.boolean().optional(),
published: z.boolean().optional(),
deleted: z.boolean().optional(),
description: z.string().min(3),
version: z.string().optional(),
runtime: z.string().optional(),
secrets: z.array(configValue).optional(),
config: z.array(configValue).optional(),
sourceUrl: z.string().url(),
code: z.string().min(3),
modules: z.array(moduleValue).optional(),
notes: z.string().optional(),
})
.strict();

function templateDirs() {
const dir = path.normalize(`${__dirname}/../../templates`);

// Read the contents of the directory
const items = fs.readdirSync(dir);

// Filter out only directories (folders)
return items
.map((item) => `${dir}/${item}`)
.filter((item) => {
return fs.statSync(item).isDirectory();
});
}

const templateToJSON = async (templateDir) => {
const yaml = yamlParser.load(
fs.readFileSync(`${templateDir}/manifest.yaml`, 'utf8')
);

const code = fs.readFileSync(`${templateDir}/code.js`, 'utf8');

return {
...yaml,
code,
};
};

const validateTemplates = async () => {
console.log('\n🗄️ Validating schema for all templates.\n');
try {
// GET ALL TEMPLATES
let templates;
try {
templates = templateDirs();
} catch (e) {
throw {
detail: 'failed to load templates',
err: e,
};
}

// BUNDLE INTO JSON
for (const templatePath of templates) {
console.log('🗄️ Validating template:', templatePath);

let template;
try {
template = await templateToJSON(templatePath);
} catch (e) {
throw {
detail: `failed to load template: ${templatePath}`,
err: e,
};
}

// VALIDATE JSON
try {
TemplateSchema.parse(template);
} catch (e) {
throw {
detail: `template validation failed, template: ${templatePath}`,
err: fromZodError(e),
};
}
}
} catch (e) {
console.error(
'⛔️ - There was an error validatong templates:',
e.detail,
'\n\n',
e.err
);
process.exit(1);
}

console.log('\n✅ Validation complete.\n');
};

(async function () {
await validateTemplates();
})();
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* Handler that will be called during the execution of a Password Reset / Post Challenge Flow.
*
* --- AUTH0 ACTIONS TEMPLATE https://github.com/auth0/os-marketplace/blob/main/templates/active-directory-groups-PASSWORD_RESET_POST_CHALLENGE ---
*
* @param {Event} event - Details about the post challenge request.
* @param {PasswordResetPostChallengeAPI} api - Interface whose methods can be used to change the behavior of the post challenge flow.
*/
exports.onExecutePostChallenge = async (event, api) => {
// ensure that the allowed group is configured
const groupAllowed = event.secrets.ALLOWED_GROUP;
if (!groupAllowed) {
return api.access.deny('Invalid configuration');
}

// get the users groups
let groups = event.user.groups || [];
if (!Array.isArray(groups)) {
groups = [groups];
}

// if the allowed group is not one of the users, deny access
if (!groups.includes(groupAllowed)) {
return api.access.deny('Access denied');
}
};

/**
* Handler that will be invoked when this action is resuming after an external redirect. If your
* onExecutePostChallenge function does not perform a redirect, this function can be safely ignored.
*
* @param {Event} event - Details about the user and the context in which they are logging in.
* @param {PasswordResetPostChallengeAPI} api - Interface whose methods can be used to change the behavior of the post challenge flow.
*/
// exports.onContinuePostChallenge = async (event, api) => {
// };
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
id: 'c9fa1544-b8bd-4211-950e-3c6ba2a946f4'
name: 'Check if a user belongs to an active directory group.'
description: 'Check if a user belongs to an AD group and if not, deny access.'
public: true
triggers:
- 'PASSWORD_RESET_POST_CHALLENGE'
runtime: 'node18'
modules: []
sourceUrl: 'https://github.com/auth0/os-marketplace/blob/main/templates/active-directory-groups-PASSWORD_RESET_POST_CHALLENGE'
notes: |
**Secrets**

* `ALLOWED_GROUP` - the name of the allowed group.
useCases:
- 'ACCESS_CONTROL'
Loading