diff --git a/extensions/directus-extension-external-jwt/package.json b/extensions/directus-extension-external-jwt/package.json new file mode 100644 index 0000000..57687f3 --- /dev/null +++ b/extensions/directus-extension-external-jwt/package.json @@ -0,0 +1,172 @@ +{ + "name": "directus-extension-external-jwt", + "description": "External JWT Directus Extension allow directus to trust tokens issued by an oauth2 or OIDC provider", + "icon": "extension", + "version": "1.0.0", + "keywords": [ + "directus", + "directus-extension", + "directus-custom-hook", + "directus-external-jwt" + ], + "homepage": "https://github.com/Zerosubnet/directus-extension-external-jwt", + "license": "LGPL-3.0-only", + "author": { + "name": "zerosubnet" + }, + "repository": { + "type": "git", + "url": "https://github.com/Zerosubnet/directus-extension-external-jwt.git" + }, + "type": "module", + "release": { + "branches": [ + "main", + "next", + { + "name": "beta", + "prerelease": true + } + ], + "plugins": [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator", + "@semantic-release/changelog", + "@semantic-release/npm", + [ + "@semantic-release/github", + { + "assets": [ + "dist/**" + ] + } + ], + [ + "@semantic-release/exec", + { + "tagImage": "docker tag ${SRCIMAGE} ${DSTIMAGE}:${nextRelease.version}", + "publishImage": "docker push ${DSTIMAGE}:${nextRelease.version}" + } + ] + ], + "preset": "angular" + }, + "publishConfig": { + "access": "public" + }, + "directus:extension": { + "type": "hook", + "path": "dist/index.js", + "source": "src/index.ts", + "host": "^10.1.7" + }, + "engines": { + "node": ">=18.0.0" + }, + "scripts": { + "build": "directus-extension build && npm run sync", + "dev": "directus-extension build -w --no-minify", + "link": "directus-extension link", + "sync": "rm -rf ./extensions/directus-extension-external-jwt && mkdir -p ./extensions/directus-extension-external-jwt/dist && ln ./package.json ./extensions/directus-extension-external-jwt/package.json && ln ./dist/index.js ./extensions/directus-extension-external-jwt/dist/index.js", + "directus": "pnpm dlx directus start", + "lint": "eslint . --ext .ts", + "test": "vitest", + "test:coverage": "vitest run --coverage" + }, + "nyc": { + "extension": [ + ".ts", + ".tsx" + ], + "reporter": [ + "text", + "lcov" + ], + "report-dir": "coverage", + "all": true, + "extends": "@istanbuljs/nyc-config-typescript", + "check-coverage": true, + "include": [ + "src/**/*.[tj]s?(x)" + ], + "exclude": [ + "src/_tests_/**/*.*", + "src/**/*.test.[tj]s?(x)" + ] + }, + "devDependencies": { + "@directus/errors": "^0.3.2", + "@directus/extensions-sdk": "^13.0.1", + "@directus/tsconfig": "^1.0.1", + "@directus/types": "^11.1.2", + "@directus/utils": "^11.0.9", + "@istanbuljs/nyc-config-typescript": "^1.0.2", + "@semantic-release/changelog": "^6.0.3", + "@semantic-release/commit-analyzer": "^10.0.4", + "@semantic-release/exec": "^6.0.3", + "@semantic-release/github": "^9.2.6", + "@semantic-release/npm": "^10.0.6", + "@types/chai": "^4.3.16", + "@types/chai-as-promised": "^7.1.8", + "@types/config": "^3.3.4", + "@types/express": "^4.17.21", + "@types/fs-extra": "^11.0.4", + "@types/js-yaml": "^4.0.9", + "@types/jsonwebtoken": "^9.0.6", + "@types/lodash-es": "^4.17.12", + "@types/mocha": "^10.0.6", + "@types/node": "^20.14.2", + "@typescript-eslint/eslint-plugin": "^5.62.0", + "@vitest/coverage-istanbul": "^0.34.6", + "axios": "^1.7.2", + "config": "^3.3.11", + "dotenv": "^16.4.5", + "eslint": "^8.57.0", + "eslint-config-standard-with-typescript": "^37.0.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-n": "^15.7.0", + "eslint-plugin-no-loops": "^0.3.0", + "eslint-plugin-promise": "^6.2.0", + "fs-extra": "^11.2.0", + "js-yaml": "^4.1.0", + "lodash-es": "^4.17.21", + "nyc": "^15.1.0", + "semantic-release": "^21.1.2", + "sqlite3": "^5.1.7", + "ts-mocha": "^10.0.0", + "ts-node": "^10.9.2", + "typescript": "^5.4.5", + "vitest": "^0.34.6" + }, + "dependencies": { + "@directus/extensions": "^3.0.5", + "@keyv/redis": "^2.8.5", + "jsonwebtoken": "^9.0.2", + "jwks-rsa": "^3.1.0", + "keyv": "^4.5.4", + "openid-client": "^5.6.5", + "uuid": "^11.1.0" + }, + "pnpm": { + "overrides": { + "vite@<4.3.9": "^4.3.9", + "vite@>4.3.9": "^4.5.3", + "zod@<=3.22.2": ">=3.22.3", + "axios@<=1.4.0": ">=1.4.1", + "axios@>=1.3.2 <=1.7.3": ">=1.7.4", + "micromatch@<4.0.8": ">=4.0.8", + "vite@>=4.0.0 <4.5.4": ">=4.5.4", + "vite@>=4.0.0 <=4.5.3": ">=4.5.4", + "rollup@>=3.0.0 <3.29.5": ">=3.29.5", + "cross-spawn@>=7.0.0 <7.0.5": ">=7.0.5", + "nanoid@<3.3.8": ">=3.3.8", + "@octokit/request-error@>=1.0.0 <5.1.1": ">=5.1.1", + "@octokit/endpoint@>=9.0.5 <9.0.6": ">=9.0.6", + "@octokit/request@>=1.0.0 <9.2.1": ">=9.2.1", + "@octokit/plugin-paginate-rest@>=1.0.0 <11.4.1": ">=11.4.1", + "serialize-javascript@<6.0.2": ">=6.0.2", + "esbuild@<=0.24.2": ">=0.25.0", + "vite@<=4.5.5": ">=4.5.6" + } + } +} \ No newline at end of file diff --git a/src/external-jwt/authProvider/get-auth-providers.ts b/src/external-jwt/authProvider/get-auth-providers.ts index 4266b6e..d7cadc7 100644 --- a/src/external-jwt/authProvider/get-auth-providers.ts +++ b/src/external-jwt/authProvider/get-auth-providers.ts @@ -29,7 +29,7 @@ export interface AuthProvider { JWKSClient?: JwksClient; use_database?: boolean; - initial_role?: string; + default_role_id?: string; } @@ -37,26 +37,31 @@ export interface AuthProvider { export async function getAuthProviders(): Promise { console.log("calling auth providers _") return new Promise((resolve, reject) => { - const authProviders: AuthProvider[] = toArray(env['AUTH_PROVIDERS']) - .filter((provider) => provider && env[`AUTH_${provider.toUpperCase()}_DRIVER`] === ('openid' || 'oauth2')) - .map((provider) => ({ - name: provider, - label: env[`AUTH_${provider.toUpperCase()}_LABEL`], - driver: env[`AUTH_${provider.toUpperCase()}_DRIVER`], - icon: env[`AUTH_${provider.toUpperCase()}_ICON`], - trusted: env[`AUTH_${provider.toUpperCase()}_TRUSTED`], - jwks_url: env[`AUTH_${provider.toUpperCase()}_JWKS_URL`], - jwks_keys: env[`AUTH_${provider.toUpperCase()}_JWKS_KEYS`], - issuer_url: env[`AUTH_${provider.toUpperCase()}_ISSUER_URL`], - admin_key: env[`AUTH_${provider.toUpperCase()}_JWT_ADMIN_KEY`], - app_key: env[`AUTH_${provider.toUpperCase()}_JWT_APP_KEY`], - role_key: env[`AUTH_${provider.toUpperCase()}_JWT_ROLE_KEY`], - client_id: env[`AUTH_${provider.toUpperCase()}_CLIENT_ID`], - client_secret: env[`AUTH_${provider.toUpperCase()}_CLIENT_SECRET`], - use_database: env[`AUTH_${provider.toUpperCase()}_JWT_USEDB`], + const authProviders: AuthProvider[] = toArray(env["AUTH_PROVIDERS"]) + .filter( + (provider) => + provider && + env[`AUTH_${provider.toUpperCase()}_DRIVER`] === + ("openid" || "oauth2") + ) + .map((provider) => ({ + name: provider, + label: env[`AUTH_${provider.toUpperCase()}_LABEL`], + driver: env[`AUTH_${provider.toUpperCase()}_DRIVER`], + icon: env[`AUTH_${provider.toUpperCase()}_ICON`], + trusted: env[`AUTH_${provider.toUpperCase()}_TRUSTED`], + jwks_url: env[`AUTH_${provider.toUpperCase()}_JWKS_URL`], + jwks_keys: env[`AUTH_${provider.toUpperCase()}_JWKS_KEYS`], + issuer_url: env[`AUTH_${provider.toUpperCase()}_ISSUER_URL`], + admin_key: env[`AUTH_${provider.toUpperCase()}_JWT_ADMIN_KEY`], + app_key: env[`AUTH_${provider.toUpperCase()}_JWT_APP_KEY`], + role_key: env[`AUTH_${provider.toUpperCase()}_JWT_ROLE_KEY`], + client_id: env[`AUTH_${provider.toUpperCase()}_CLIENT_ID`], + client_secret: env[`AUTH_${provider.toUpperCase()}_CLIENT_SECRET`], + use_database: env[`AUTH_${provider.toUpperCase()}_JWT_USEDB`], - initial_role: env[`AUTH_${provider.toUpperCase()}_INITIAL_ROLE`] - })); + default_role_id: env[`AUTH_${provider.toUpperCase()}_DEFAULT_ROLE_ID`], + })); if(authProviders.length === 0) return resolve([]); diff --git a/src/external-jwt/get-accountability-for-token.ts b/src/external-jwt/get-accountability-for-token.ts index 5edee47..5ccd040 100644 --- a/src/external-jwt/get-accountability-for-token.ts +++ b/src/external-jwt/get-accountability-for-token.ts @@ -40,8 +40,8 @@ const getUser = async ( .first(); }; -const insertUser = async (database: Knex, user: Record) => { - return database("directus_users").insert(user).returning("*"); +const insertUser = async (database: Knex, user: Record): Promise => { + return database("directus_users").insert(user); }; // TODO: optimize this function, reduce the amount of loops @@ -51,14 +51,6 @@ export async function getAccountabilityForToken( accountability: Accountability | null, database: Knex ): Promise { - console.log( - "getAccountabilityForToken called with token", - token, - "and iss", - iss, - "and accountability", - accountability - ); if (accountability == null) { accountability = { user: null, @@ -103,10 +95,9 @@ export async function getAccountabilityForToken( console.debug("User found in database:", user); if (!user) { - const role = provider.initial_role user = await insertUser(database, { id: uuid.v4(), - role: role, + role: provider.default_role_id, provider: provider.name, external_identifier: result.sub, }); @@ -126,11 +117,6 @@ export async function getAccountabilityForToken( CacheSet(result.sub, accountability); } - console.log( - "Returning accountability from database:", - accountability - ); - return accountability; } } catch (error) {