@naylence/advanced-security 0.4.5 → 0.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/dist/browser/index.cjs +503 -16
  2. package/dist/browser/index.mjs +504 -17
  3. package/dist/cjs/advanced-security-isomorphic.js +1 -1
  4. package/dist/cjs/advanced-security-isomorphic.js.map +1 -1
  5. package/dist/cjs/naylence/fame/factory-manifest.js +2 -0
  6. package/dist/cjs/naylence/fame/factory-manifest.js.map +1 -1
  7. package/dist/cjs/naylence/fame/security/auth/index.js +2 -0
  8. package/dist/cjs/naylence/fame/security/auth/index.js.map +1 -1
  9. package/dist/cjs/naylence/fame/security/auth/policy/auth-policy-server-cli.js +47 -0
  10. package/dist/cjs/naylence/fame/security/auth/policy/auth-policy-server-cli.js.map +1 -0
  11. package/dist/cjs/naylence/fame/security/auth/policy/auth-policy-server.js +562 -0
  12. package/dist/cjs/naylence/fame/security/auth/policy/auth-policy-server.js.map +1 -0
  13. package/dist/cjs/naylence/fame/security/auth/policy/http-authorization-policy-source-factory.js +108 -0
  14. package/dist/cjs/naylence/fame/security/auth/policy/http-authorization-policy-source-factory.js.map +1 -0
  15. package/dist/cjs/naylence/fame/security/auth/policy/http-authorization-policy-source.js +367 -0
  16. package/dist/cjs/naylence/fame/security/auth/policy/http-authorization-policy-source.js.map +1 -0
  17. package/dist/cjs/naylence/fame/security/auth/policy/index.js +4 -2
  18. package/dist/cjs/naylence/fame/security/auth/policy/index.js.map +1 -1
  19. package/dist/cjs/naylence/fame/security/auth/policy-http-authorization-profile.js +78 -0
  20. package/dist/cjs/naylence/fame/security/auth/policy-http-authorization-profile.js.map +1 -0
  21. package/dist/cjs/naylence/fame/security/register-advanced-security-factories.js +2 -0
  22. package/dist/cjs/naylence/fame/security/register-advanced-security-factories.js.map +1 -1
  23. package/dist/cjs/version.js +2 -2
  24. package/dist/esm/advanced-security-isomorphic.js +1 -1
  25. package/dist/esm/advanced-security-isomorphic.js.map +1 -1
  26. package/dist/esm/naylence/fame/factory-manifest.js +2 -0
  27. package/dist/esm/naylence/fame/factory-manifest.js.map +1 -1
  28. package/dist/esm/naylence/fame/security/auth/index.js +2 -0
  29. package/dist/esm/naylence/fame/security/auth/index.js.map +1 -1
  30. package/dist/esm/naylence/fame/security/auth/policy/auth-policy-server-cli.js +47 -0
  31. package/dist/esm/naylence/fame/security/auth/policy/auth-policy-server-cli.js.map +1 -0
  32. package/dist/esm/naylence/fame/security/auth/policy/auth-policy-server.js +562 -0
  33. package/dist/esm/naylence/fame/security/auth/policy/auth-policy-server.js.map +1 -0
  34. package/dist/esm/naylence/fame/security/auth/policy/http-authorization-policy-source-factory.js +108 -0
  35. package/dist/esm/naylence/fame/security/auth/policy/http-authorization-policy-source-factory.js.map +1 -0
  36. package/dist/esm/naylence/fame/security/auth/policy/http-authorization-policy-source.js +367 -0
  37. package/dist/esm/naylence/fame/security/auth/policy/http-authorization-policy-source.js.map +1 -0
  38. package/dist/esm/naylence/fame/security/auth/policy/index.js +4 -2
  39. package/dist/esm/naylence/fame/security/auth/policy/index.js.map +1 -1
  40. package/dist/esm/naylence/fame/security/auth/policy-http-authorization-profile.js +78 -0
  41. package/dist/esm/naylence/fame/security/auth/policy-http-authorization-profile.js.map +1 -0
  42. package/dist/esm/naylence/fame/security/register-advanced-security-factories.js +2 -0
  43. package/dist/esm/naylence/fame/security/register-advanced-security-factories.js.map +1 -1
  44. package/dist/esm/version.js +2 -2
  45. package/dist/node/index.cjs +578 -125
  46. package/dist/node/index.mjs +572 -94
  47. package/dist/node/node.cjs +589 -51
  48. package/dist/node/node.mjs +582 -19
  49. package/dist/types/advanced-security-isomorphic.d.ts +0 -1
  50. package/dist/types/advanced-security-isomorphic.d.ts.map +1 -1
  51. package/dist/types/naylence/fame/factory-manifest.d.ts +1 -1
  52. package/dist/types/naylence/fame/factory-manifest.d.ts.map +1 -1
  53. package/dist/types/naylence/fame/security/auth/index.d.ts +1 -0
  54. package/dist/types/naylence/fame/security/auth/index.d.ts.map +1 -1
  55. package/dist/types/naylence/fame/security/auth/policy/auth-policy-server-cli.d.ts +20 -0
  56. package/dist/types/naylence/fame/security/auth/policy/auth-policy-server-cli.d.ts.map +1 -0
  57. package/dist/types/naylence/fame/security/auth/policy/auth-policy-server.d.ts +75 -0
  58. package/dist/types/naylence/fame/security/auth/policy/auth-policy-server.d.ts.map +1 -0
  59. package/dist/types/naylence/fame/security/auth/policy/http-authorization-policy-source-factory.d.ts +81 -0
  60. package/dist/types/naylence/fame/security/auth/policy/http-authorization-policy-source-factory.d.ts.map +1 -0
  61. package/dist/types/naylence/fame/security/auth/policy/http-authorization-policy-source.d.ts +150 -0
  62. package/dist/types/naylence/fame/security/auth/policy/http-authorization-policy-source.d.ts.map +1 -0
  63. package/dist/types/naylence/fame/security/auth/policy/index.d.ts +2 -1
  64. package/dist/types/naylence/fame/security/auth/policy/index.d.ts.map +1 -1
  65. package/dist/types/naylence/fame/security/auth/policy-http-authorization-profile.d.ts +17 -0
  66. package/dist/types/naylence/fame/security/auth/policy-http-authorization-profile.d.ts.map +1 -0
  67. package/dist/types/naylence/fame/security/register-advanced-security-factories.d.ts +1 -0
  68. package/dist/types/naylence/fame/security/register-advanced-security-factories.d.ts.map +1 -1
  69. package/dist/types/version.d.ts +1 -1
  70. package/package.json +3 -2
@@ -6,6 +6,7 @@
6
6
  */
7
7
  export const MODULES = [
8
8
  "./security/auth/policy/advanced-authorization-policy-factory.js",
9
+ "./security/auth/policy/http-authorization-policy-source-factory.js",
9
10
  "./security/cert/default-ca-service-factory.js",
10
11
  "./security/cert/default-certificate-manager-factory.js",
11
12
  "./security/cert/trust-store/browser-trust-store-provider-factory.js",
@@ -23,6 +24,7 @@ export const MODULES = [
23
24
  ];
24
25
  export const MODULE_LOADERS = {
25
26
  "./security/auth/policy/advanced-authorization-policy-factory.js": () => import("./security/auth/policy/advanced-authorization-policy-factory.js"),
27
+ "./security/auth/policy/http-authorization-policy-source-factory.js": () => import("./security/auth/policy/http-authorization-policy-source-factory.js"),
26
28
  "./security/cert/default-ca-service-factory.js": () => import("./security/cert/default-ca-service-factory.js"),
27
29
  "./security/cert/default-certificate-manager-factory.js": () => import("./security/cert/default-certificate-manager-factory.js"),
28
30
  "./security/cert/trust-store/browser-trust-store-provider-factory.js": () => import("./security/cert/trust-store/browser-trust-store-provider-factory.js"),
@@ -1 +1 @@
1
- {"version":3,"file":"factory-manifest.js","sourceRoot":"","sources":["../../../../src/naylence/fame/factory-manifest.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,iEAAiE;IACjE,+CAA+C;IAC/C,wDAAwD;IACxD,qEAAqE;IACrE,kEAAkE;IAClE,qEAAqE;IACrE,+DAA+D;IAC/D,iEAAiE;IACjE,mEAAmE;IACnE,4CAA4C;IAC5C,qDAAqD;IACrD,uDAAuD;IACvD,8DAA8D;IAC9D,wDAAwD;IACxD,+CAA+C;CACvC,CAAC;AAKX,MAAM,CAAC,MAAM,cAAc,GAAmD;IAC5E,iEAAiE,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,iEAAiE,CAAC;IAClJ,+CAA+C,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,+CAA+C,CAAC;IAC9G,wDAAwD,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,wDAAwD,CAAC;IAChI,qEAAqE,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,qEAAqE,CAAC;IAC1J,kEAAkE,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,kEAAkE,CAAC;IACpJ,qEAAqE,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,qEAAqE,CAAC;IAC1J,+DAA+D,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,+DAA+D,CAAC;IAC9I,iEAAiE,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,iEAAiE,CAAC;IAClJ,mEAAmE,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,mEAAmE,CAAC;IACtJ,4CAA4C,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,4CAA4C,CAAC;IACxG,qDAAqD,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,qDAAqD,CAAC;IAC1H,uDAAuD,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,uDAAuD,CAAC;IAC9H,8DAA8D,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,8DAA8D,CAAC;IAC5I,wDAAwD,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,wDAAwD,CAAC;IAChI,+CAA+C,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,+CAA+C,CAAC;CAC/G,CAAC"}
1
+ {"version":3,"file":"factory-manifest.js","sourceRoot":"","sources":["../../../../src/naylence/fame/factory-manifest.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,iEAAiE;IACjE,oEAAoE;IACpE,+CAA+C;IAC/C,wDAAwD;IACxD,qEAAqE;IACrE,kEAAkE;IAClE,qEAAqE;IACrE,+DAA+D;IAC/D,iEAAiE;IACjE,mEAAmE;IACnE,4CAA4C;IAC5C,qDAAqD;IACrD,uDAAuD;IACvD,8DAA8D;IAC9D,wDAAwD;IACxD,+CAA+C;CACvC,CAAC;AAKX,MAAM,CAAC,MAAM,cAAc,GAAmD;IAC5E,iEAAiE,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,iEAAiE,CAAC;IAClJ,oEAAoE,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,oEAAoE,CAAC;IACxJ,+CAA+C,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,+CAA+C,CAAC;IAC9G,wDAAwD,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,wDAAwD,CAAC;IAChI,qEAAqE,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,qEAAqE,CAAC;IAC1J,kEAAkE,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,kEAAkE,CAAC;IACpJ,qEAAqE,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,qEAAqE,CAAC;IAC1J,+DAA+D,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,+DAA+D,CAAC;IAC9I,iEAAiE,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,iEAAiE,CAAC;IAClJ,mEAAmE,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,mEAAmE,CAAC;IACtJ,4CAA4C,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,4CAA4C,CAAC;IACxG,qDAAqD,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,qDAAqD,CAAC;IAC1H,uDAAuD,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,uDAAuD,CAAC;IAC9H,8DAA8D,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,8DAA8D,CAAC;IAC5I,wDAAwD,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,wDAAwD,CAAC;IAChI,+CAA+C,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,+CAA+C,CAAC;CAC/G,CAAC"}
@@ -4,4 +4,6 @@
4
4
  * @packageDocumentation
5
5
  */
6
6
  export * from "./policy/index.js";
7
+ // Authorization profiles
8
+ export { PROFILE_NAME_POLICY_HTTP, ENV_VAR_AUTH_POLICY_URL, ENV_VAR_AUTH_POLICY_TIMEOUT_MS, ENV_VAR_AUTH_POLICY_CACHE_TTL_MS, ENV_VAR_AUTH_POLICY_BEARER_TOKEN, } from "./policy-http-authorization-profile.js";
7
9
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../src/naylence/fame/security/auth/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../src/naylence/fame/security/auth/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,mBAAmB,CAAC;AAElC,yBAAyB;AACzB,OAAO,EACL,wBAAwB,EACxB,uBAAuB,EACvB,8BAA8B,EAC9B,gCAAgC,EAChC,gCAAgC,GACjC,MAAM,wCAAwC,CAAC"}
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Auth Policy Server CLI Entry Point
4
+ *
5
+ * Development server for serving authorization policies over HTTP.
6
+ * Useful for testing HttpAuthorizationPolicySource.
7
+ *
8
+ * Environment variables:
9
+ * FAME_APP_HOST - Host to bind to (default: 0.0.0.0)
10
+ * FAME_APP_PORT - Port to listen on (default: 8099)
11
+ * FAME_POLICY_FILE - Path to policy file (YAML or JSON)
12
+ * FAME_POLICY_BEARER_TOKEN - Bearer token for authentication
13
+ * FAME_LOG_LEVEL - Log level (debug, info, warning, error)
14
+ *
15
+ * Usage:
16
+ * npx naylence-policy-server
17
+ * FAME_POLICY_FILE=./policy.yaml FAME_POLICY_BEARER_TOKEN=secret npx naylence-policy-server
18
+ */
19
+ import { pathToFileURL } from "node:url";
20
+ import { main } from "./auth-policy-server.js";
21
+ function isDirectExecution() {
22
+ if (typeof process === "undefined") {
23
+ return false;
24
+ }
25
+ const entry = process.argv?.[1];
26
+ if (typeof entry !== "string" || entry.length === 0) {
27
+ return false;
28
+ }
29
+ const entryUrl = pathToFileURL(entry).href;
30
+ return import.meta.url === entryUrl;
31
+ }
32
+ function registerSignalHandlers() {
33
+ const handleShutdown = (signal) => {
34
+ console.log("[INFO] auth_policy_server_shutting_down", { signal });
35
+ process.exit(0);
36
+ };
37
+ process.on("SIGTERM", () => handleShutdown("SIGTERM"));
38
+ process.on("SIGINT", () => handleShutdown("SIGINT"));
39
+ }
40
+ if (isDirectExecution()) {
41
+ registerSignalHandlers();
42
+ main().catch((error) => {
43
+ console.error("Fatal error:", error);
44
+ process.exit(1);
45
+ });
46
+ }
47
+ //# sourceMappingURL=auth-policy-server-cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-policy-server-cli.js","sourceRoot":"","sources":["../../../../../../../src/naylence/fame/security/auth/policy/auth-policy-server-cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAE/C,SAAS,iBAAiB;IACxB,IAAI,OAAO,OAAO,KAAK,WAAW,EAAE,CAAC;QACnC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAChC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,QAAQ,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC;IAC3C,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,QAAQ,CAAC;AACtC,CAAC;AAED,SAAS,sBAAsB;IAC7B,MAAM,cAAc,GAAG,CAAC,MAAsB,EAAE,EAAE;QAChD,OAAO,CAAC,GAAG,CAAC,yCAAyC,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;QACnE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC;IAEF,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,CAAC;IACvD,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC;AACvD,CAAC;AAED,IAAI,iBAAiB,EAAE,EAAE,CAAC;IACxB,sBAAsB,EAAE,CAAC;IAEzB,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;QACrB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;QACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,562 @@
1
+ /**
2
+ * Auth Policy Server - Authorization Policy HTTP endpoint
3
+ *
4
+ * Provides authorization policies via HTTP using Fastify.
5
+ * Supports OAuth2 JWT authentication and ETag-based caching.
6
+ * Serves multiple policies by ID from a configurable directory.
7
+ * This is a development server for testing HTTP policy source functionality.
8
+ *
9
+ * Policy files should be named: policy-{policy_id}.yaml or policy-{policy_id}.json
10
+ * Example: policy-production.yaml, policy-dev.json
11
+ *
12
+ * Authentication:
13
+ * - Set FAME_OAUTH2_ISSUER to enable OAuth2 JWT validation
14
+ * - Optionally set FAME_OAUTH2_AUDIENCE, FAME_OAUTH2_JWKS_URL
15
+ * - Set FAME_OAUTH2_ALGORITHMS to customize JWT algorithms (default: RS256,ES256,EdDSA)
16
+ * - If no OAuth2 config provided, authentication is disabled (dev mode)
17
+ */
18
+ import { createHash } from "node:crypto";
19
+ import Fastify from "fastify";
20
+ import { realpathSync, readFileSync, existsSync, statSync, readdirSync, watch, } from "node:fs";
21
+ import { resolve, extname, join } from "node:path";
22
+ import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
23
+ // Simple console logger for policy server
24
+ const logger = {
25
+ info: (event, meta) => {
26
+ console.log(`[INFO] ${event}`, meta || "");
27
+ },
28
+ warning: (event, meta) => {
29
+ console.warn(`[WARNING] ${event}`, meta || "");
30
+ },
31
+ error: (event, meta) => {
32
+ console.error(`[ERROR] ${event}`, meta || "");
33
+ },
34
+ debug: (event, meta) => {
35
+ const logLevel = (process.env.FAME_LOG_LEVEL || "info").toLowerCase();
36
+ if (logLevel === "debug" || logLevel === "trace") {
37
+ console.log(`[DEBUG] ${event}`, meta || "");
38
+ }
39
+ },
40
+ };
41
+ // Environment variables
42
+ const ENV_VAR_FAME_APP_HOST = "FAME_APP_HOST";
43
+ const ENV_VAR_FAME_APP_PORT = "FAME_APP_PORT";
44
+ const ENV_VAR_FAME_POLICY_DIR = "FAME_POLICY_DIR";
45
+ // OAuth2 configuration
46
+ const ENV_VAR_OAUTH2_ISSUER = "FAME_OAUTH2_ISSUER";
47
+ const ENV_VAR_OAUTH2_AUDIENCE = "FAME_OAUTH2_AUDIENCE";
48
+ const ENV_VAR_OAUTH2_JWKS_URL = "FAME_OAUTH2_JWKS_URL";
49
+ const ENV_VAR_OAUTH2_REQUIRED_SCOPES = "FAME_OAUTH2_REQUIRED_SCOPES";
50
+ const ENV_VAR_OAUTH2_ALGORITHMS = "FAME_OAUTH2_ALGORITHMS";
51
+ // Default algorithms for JWT verification (matches @naylence/runtime defaults)
52
+ const DEFAULT_JWT_ALGORITHMS = ["RS256", "ES256", "EdDSA"];
53
+ // Policy file naming pattern: policy-{id}.yaml or policy-{id}.json
54
+ const POLICY_FILE_PATTERN = /^policy-(.+)\.(ya?ml|json)$/i;
55
+ /**
56
+ * Default authorization policy for development.
57
+ * Allows all operations - suitable for testing only.
58
+ */
59
+ const DEFAULT_POLICY = {
60
+ version: "1",
61
+ type: "AdvancedAuthorizationPolicy",
62
+ default_effect: "deny",
63
+ rules: [
64
+ {
65
+ id: "allow-all-dev",
66
+ effect: "allow",
67
+ comment: "Development policy - allows all operations",
68
+ },
69
+ ],
70
+ };
71
+ /**
72
+ * Compute ETag from content using SHA-256.
73
+ */
74
+ function computeEtag(content) {
75
+ const hash = createHash("sha256");
76
+ hash.update(content, "utf-8");
77
+ return `"${hash.digest("hex").substring(0, 16)}"`;
78
+ }
79
+ /**
80
+ * Extract policy ID from filename.
81
+ * Expected format: policy-{id}.yaml or policy-{id}.json
82
+ */
83
+ function extractPolicyId(filename) {
84
+ const match = POLICY_FILE_PATTERN.exec(filename);
85
+ if (!match) {
86
+ return null;
87
+ }
88
+ const id = match[1];
89
+ const ext = match[2]?.toLowerCase();
90
+ const format = ext === "json" ? "json" : "yaml";
91
+ return { id, format };
92
+ }
93
+ /**
94
+ * Load a single policy from file.
95
+ */
96
+ function loadPolicyFile(filePath) {
97
+ try {
98
+ const content = readFileSync(filePath, "utf-8");
99
+ const ext = extname(filePath).toLowerCase();
100
+ let policy;
101
+ if (ext === ".json") {
102
+ policy = JSON.parse(content);
103
+ }
104
+ else {
105
+ policy = parseYaml(content);
106
+ }
107
+ const stats = statSync(filePath);
108
+ const format = ext === ".json" ? "json" : "yaml";
109
+ return {
110
+ policy,
111
+ policyContent: content,
112
+ etag: computeEtag(content),
113
+ lastModified: stats.mtime,
114
+ filePath,
115
+ format,
116
+ };
117
+ }
118
+ catch (error) {
119
+ logger.error("policy_file_load_error", {
120
+ path: filePath,
121
+ error: error instanceof Error ? error.message : String(error),
122
+ });
123
+ return null;
124
+ }
125
+ }
126
+ /**
127
+ * Load all policies from a directory.
128
+ */
129
+ function loadPoliciesFromDir(dirPath) {
130
+ const policies = new Map();
131
+ if (!existsSync(dirPath)) {
132
+ logger.warning("policy_directory_not_found", { path: dirPath });
133
+ return policies;
134
+ }
135
+ const files = readdirSync(dirPath);
136
+ for (const filename of files) {
137
+ const parsed = extractPolicyId(filename);
138
+ if (!parsed) {
139
+ continue;
140
+ }
141
+ const filePath = join(dirPath, filename);
142
+ const entry = loadPolicyFile(filePath);
143
+ if (entry) {
144
+ policies.set(parsed.id, {
145
+ id: parsed.id,
146
+ ...entry,
147
+ });
148
+ logger.info("policy_loaded", { id: parsed.id, path: filePath });
149
+ }
150
+ }
151
+ return policies;
152
+ }
153
+ /**
154
+ * Create default policy entry.
155
+ */
156
+ function createDefaultPolicyEntry() {
157
+ const content = JSON.stringify(DEFAULT_POLICY, null, 2);
158
+ return {
159
+ id: "default",
160
+ policy: DEFAULT_POLICY,
161
+ policyContent: content,
162
+ etag: computeEtag(content),
163
+ lastModified: new Date(),
164
+ filePath: "(built-in)",
165
+ format: "json",
166
+ };
167
+ }
168
+ /**
169
+ * Create an OAuth2 token verifier from environment configuration.
170
+ */
171
+ async function createTokenVerifier() {
172
+ const issuer = process.env[ENV_VAR_OAUTH2_ISSUER];
173
+ if (!issuer) {
174
+ return null;
175
+ }
176
+ try {
177
+ // Dynamically import the token verifier factory from @naylence/runtime
178
+ const { TokenVerifierFactory } = await import("@naylence/runtime");
179
+ const jwksUrl = process.env[ENV_VAR_OAUTH2_JWKS_URL] ||
180
+ `${issuer.replace(/\/+$/, "")}/.well-known/jwks.json`;
181
+ // Parse algorithms from environment or use defaults
182
+ const algorithmsEnv = process.env[ENV_VAR_OAUTH2_ALGORITHMS];
183
+ const algorithms = algorithmsEnv
184
+ ? algorithmsEnv.split(",").map((a) => a.trim()).filter(Boolean)
185
+ : DEFAULT_JWT_ALGORITHMS;
186
+ const config = {
187
+ type: "JWKSJWTTokenVerifier",
188
+ issuer,
189
+ jwks_url: jwksUrl,
190
+ algorithms,
191
+ };
192
+ const audience = process.env[ENV_VAR_OAUTH2_AUDIENCE];
193
+ if (audience) {
194
+ config.audience = audience;
195
+ }
196
+ const verifier = await TokenVerifierFactory.createTokenVerifier(config);
197
+ logger.info("oauth2_token_verifier_created", { issuer, jwksUrl });
198
+ return verifier;
199
+ }
200
+ catch (error) {
201
+ logger.error("failed_to_create_token_verifier", {
202
+ error: error instanceof Error ? error.message : String(error),
203
+ });
204
+ return null;
205
+ }
206
+ }
207
+ /**
208
+ * Authenticate request using OAuth2 JWT verification.
209
+ */
210
+ async function authenticateRequest(request, tokenVerifier) {
211
+ if (!tokenVerifier) {
212
+ // No auth required (dev mode)
213
+ return { authenticated: true };
214
+ }
215
+ const authHeader = request.headers.authorization;
216
+ if (!authHeader) {
217
+ return { authenticated: false, error: "Missing Authorization header" };
218
+ }
219
+ const parts = authHeader.split(" ");
220
+ if (parts.length !== 2 || parts[0]?.toLowerCase() !== "bearer") {
221
+ return { authenticated: false, error: "Invalid Authorization header format" };
222
+ }
223
+ const token = parts[1];
224
+ if (!token) {
225
+ return { authenticated: false, error: "Missing bearer token" };
226
+ }
227
+ try {
228
+ const claims = await tokenVerifier.verify(token);
229
+ // Check required scopes if configured
230
+ const requiredScopesEnv = process.env[ENV_VAR_OAUTH2_REQUIRED_SCOPES];
231
+ if (requiredScopesEnv) {
232
+ const requiredScopes = requiredScopesEnv.split(",").map((s) => s.trim());
233
+ const tokenScopes = typeof claims.scope === "string"
234
+ ? claims.scope.split(" ")
235
+ : Array.isArray(claims.scp)
236
+ ? claims.scp
237
+ : [];
238
+ const hasAllScopes = requiredScopes.every((required) => tokenScopes.includes(required));
239
+ if (!hasAllScopes) {
240
+ return {
241
+ authenticated: false,
242
+ error: `Missing required scopes: ${requiredScopes.join(", ")}`,
243
+ };
244
+ }
245
+ }
246
+ logger.debug("jwt_token_verified", { sub: claims.sub });
247
+ return { authenticated: true, claims };
248
+ }
249
+ catch (error) {
250
+ logger.warning("jwt_verification_failed", {
251
+ error: error instanceof Error ? error.message : String(error),
252
+ });
253
+ return {
254
+ authenticated: false,
255
+ error: error instanceof Error ? error.message : "Token verification failed",
256
+ };
257
+ }
258
+ }
259
+ /**
260
+ * Create policy router with authorization policy endpoints.
261
+ * Supports fetching policies by ID from: /fame/v1/auth-policies/:policyId
262
+ */
263
+ function createPolicyRouter(fastify, state, tokenVerifier, prefix = "/fame/v1") {
264
+ const policiesPath = `${prefix}/auth-policies`;
265
+ // List all available policies
266
+ fastify.get(policiesPath, async (request, reply) => {
267
+ const authResult = await authenticateRequest(request, tokenVerifier);
268
+ if (!authResult.authenticated) {
269
+ logger.warning("authentication_failed", { error: authResult.error });
270
+ return reply.status(401).send({
271
+ error: "unauthorized",
272
+ message: authResult.error,
273
+ });
274
+ }
275
+ const policyList = Array.from(state.policies.values()).map((entry) => ({
276
+ id: entry.id,
277
+ lastModified: entry.lastModified.toISOString(),
278
+ format: entry.format,
279
+ }));
280
+ return reply
281
+ .header("Content-Type", "application/json")
282
+ .send({ policies: policyList });
283
+ });
284
+ // Get policy by ID (auto-detect format based on Accept header or original format)
285
+ fastify.get(`${policiesPath}/:policyId`, async (request, reply) => {
286
+ const authResult = await authenticateRequest(request, tokenVerifier);
287
+ if (!authResult.authenticated) {
288
+ logger.warning("authentication_failed", { error: authResult.error });
289
+ return reply.status(401).send({
290
+ error: "unauthorized",
291
+ message: authResult.error,
292
+ });
293
+ }
294
+ const { policyId } = request.params;
295
+ const entry = state.policies.get(policyId);
296
+ if (!entry) {
297
+ logger.warning("policy_not_found", { policyId });
298
+ return reply.status(404).send({
299
+ error: "not_found",
300
+ message: `Policy '${policyId}' not found`,
301
+ availablePolicies: Array.from(state.policies.keys()),
302
+ });
303
+ }
304
+ // Check ETag for conditional request
305
+ const ifNoneMatch = request.headers["if-none-match"];
306
+ if (ifNoneMatch && ifNoneMatch === entry.etag) {
307
+ logger.debug("returning_304_not_modified", { policyId, etag: entry.etag });
308
+ return reply
309
+ .status(304)
310
+ .header("ETag", entry.etag)
311
+ .header("Cache-Control", "public, max-age=60, stale-while-revalidate=300")
312
+ .send();
313
+ }
314
+ logger.debug("serving_policy", {
315
+ policyId,
316
+ etag: entry.etag,
317
+ lastModified: entry.lastModified.toISOString(),
318
+ });
319
+ // Determine content type based on Accept header or original format
320
+ const acceptHeader = request.headers.accept || "";
321
+ const isYamlRequest = acceptHeader.includes("yaml") || acceptHeader.includes("text/plain");
322
+ let contentType;
323
+ let responseBody;
324
+ if (isYamlRequest || entry.format === "yaml") {
325
+ contentType = "application/yaml";
326
+ responseBody =
327
+ entry.format === "yaml"
328
+ ? entry.policyContent
329
+ : stringifyYaml(entry.policy);
330
+ }
331
+ else {
332
+ contentType = "application/json";
333
+ responseBody = JSON.stringify(entry.policy, null, 2);
334
+ }
335
+ return reply
336
+ .header("Content-Type", contentType)
337
+ .header("ETag", entry.etag)
338
+ .header("Last-Modified", entry.lastModified.toUTCString())
339
+ .header("Cache-Control", "public, max-age=60, stale-while-revalidate=300")
340
+ .send(responseBody);
341
+ });
342
+ // Get policy by ID as YAML
343
+ fastify.get(`${policiesPath}/:policyId.yaml`, async (request, reply) => {
344
+ const authResult = await authenticateRequest(request, tokenVerifier);
345
+ if (!authResult.authenticated) {
346
+ return reply.status(401).send({
347
+ error: "unauthorized",
348
+ message: authResult.error,
349
+ });
350
+ }
351
+ const { policyId } = request.params;
352
+ const entry = state.policies.get(policyId);
353
+ if (!entry) {
354
+ return reply.status(404).send({
355
+ error: "not_found",
356
+ message: `Policy '${policyId}' not found`,
357
+ });
358
+ }
359
+ const ifNoneMatch = request.headers["if-none-match"];
360
+ if (ifNoneMatch && ifNoneMatch === entry.etag) {
361
+ return reply
362
+ .status(304)
363
+ .header("ETag", entry.etag)
364
+ .header("Cache-Control", "public, max-age=60, stale-while-revalidate=300")
365
+ .send();
366
+ }
367
+ const yamlContent = entry.format === "yaml" ? entry.policyContent : stringifyYaml(entry.policy);
368
+ return reply
369
+ .header("Content-Type", "application/yaml")
370
+ .header("ETag", entry.etag)
371
+ .header("Last-Modified", entry.lastModified.toUTCString())
372
+ .header("Cache-Control", "public, max-age=60, stale-while-revalidate=300")
373
+ .send(yamlContent);
374
+ });
375
+ // Get policy by ID as JSON
376
+ fastify.get(`${policiesPath}/:policyId.json`, async (request, reply) => {
377
+ const authResult = await authenticateRequest(request, tokenVerifier);
378
+ if (!authResult.authenticated) {
379
+ return reply.status(401).send({
380
+ error: "unauthorized",
381
+ message: authResult.error,
382
+ });
383
+ }
384
+ const { policyId } = request.params;
385
+ const entry = state.policies.get(policyId);
386
+ if (!entry) {
387
+ return reply.status(404).send({
388
+ error: "not_found",
389
+ message: `Policy '${policyId}' not found`,
390
+ });
391
+ }
392
+ const ifNoneMatch = request.headers["if-none-match"];
393
+ if (ifNoneMatch && ifNoneMatch === entry.etag) {
394
+ return reply
395
+ .status(304)
396
+ .header("ETag", entry.etag)
397
+ .header("Cache-Control", "public, max-age=60, stale-while-revalidate=300")
398
+ .send();
399
+ }
400
+ return reply
401
+ .header("Content-Type", "application/json")
402
+ .header("ETag", entry.etag)
403
+ .header("Last-Modified", entry.lastModified.toUTCString())
404
+ .header("Cache-Control", "public, max-age=60, stale-while-revalidate=300")
405
+ .send(entry.policy);
406
+ });
407
+ // Health check
408
+ fastify.get("/health", async () => {
409
+ return {
410
+ status: "healthy",
411
+ service: "auth-policy-server",
412
+ policyDirectory: state.policyDir || "(using defaults)",
413
+ policyCount: state.policies.size,
414
+ policies: Array.from(state.policies.keys()),
415
+ };
416
+ });
417
+ }
418
+ /**
419
+ * Create Fastify application with policy server.
420
+ */
421
+ async function createApp() {
422
+ const fastify = Fastify({
423
+ logger: false,
424
+ });
425
+ // Load policies from directory or use default
426
+ const policyDir = process.env[ENV_VAR_FAME_POLICY_DIR];
427
+ const state = {
428
+ policyDir,
429
+ policies: new Map(),
430
+ };
431
+ if (policyDir && existsSync(policyDir)) {
432
+ state.policies = loadPoliciesFromDir(policyDir);
433
+ // Watch directory for changes
434
+ watch(policyDir, (eventType, filename) => {
435
+ if (!filename)
436
+ return;
437
+ const parsed = extractPolicyId(filename);
438
+ if (!parsed)
439
+ return;
440
+ const filePath = join(policyDir, filename);
441
+ if (eventType === "rename") {
442
+ // File added or removed
443
+ if (existsSync(filePath)) {
444
+ const entry = loadPolicyFile(filePath);
445
+ if (entry) {
446
+ state.policies.set(parsed.id, { id: parsed.id, ...entry });
447
+ logger.info("policy_added", { id: parsed.id, path: filePath });
448
+ }
449
+ }
450
+ else {
451
+ state.policies.delete(parsed.id);
452
+ logger.info("policy_removed", { id: parsed.id });
453
+ }
454
+ }
455
+ else if (eventType === "change") {
456
+ // File modified
457
+ const entry = loadPolicyFile(filePath);
458
+ if (entry) {
459
+ state.policies.set(parsed.id, { id: parsed.id, ...entry });
460
+ logger.info("policy_reloaded", { id: parsed.id, path: filePath });
461
+ }
462
+ }
463
+ });
464
+ logger.info("watching_policy_directory", { path: policyDir });
465
+ }
466
+ // Always add a default policy
467
+ if (state.policies.size === 0) {
468
+ state.policies.set("default", createDefaultPolicyEntry());
469
+ logger.info("using_default_policy", {
470
+ reason: policyDir ? "no_policies_found_in_directory" : "no_directory_specified",
471
+ });
472
+ }
473
+ // Create OAuth2 token verifier if configured
474
+ const tokenVerifier = await createTokenVerifier();
475
+ // Register policy router
476
+ createPolicyRouter(fastify, state, tokenVerifier);
477
+ if (tokenVerifier) {
478
+ logger.info("oauth2_jwt_auth_enabled", {
479
+ issuer: process.env[ENV_VAR_OAUTH2_ISSUER],
480
+ });
481
+ }
482
+ else {
483
+ logger.warning("auth_disabled", {
484
+ message: "Set FAME_OAUTH2_ISSUER to enable OAuth2 JWT authentication",
485
+ });
486
+ }
487
+ return { app: fastify, state };
488
+ }
489
+ async function main() {
490
+ try {
491
+ const { app, state } = await createApp();
492
+ const host = process.env[ENV_VAR_FAME_APP_HOST] || "0.0.0.0";
493
+ const port = parseInt(process.env[ENV_VAR_FAME_APP_PORT] || "8099", 10);
494
+ await app.listen({ host, port });
495
+ const issuer = process.env[ENV_VAR_OAUTH2_ISSUER];
496
+ logger.info("auth_policy_server_started", { host, port });
497
+ console.log(`\nšŸ“ Auth Policy Server listening on http://${host}:${port}`);
498
+ console.log(`šŸ“‹ List policies: http://${host}:${port}/fame/v1/auth-policies`);
499
+ console.log(`šŸ“‹ Get policy: http://${host}:${port}/fame/v1/auth-policies/{policy_id}`);
500
+ console.log(`šŸ“‹ Get as YAML: http://${host}:${port}/fame/v1/auth-policies/{policy_id}.yaml`);
501
+ console.log(`šŸ“‹ Get as JSON: http://${host}:${port}/fame/v1/auth-policies/{policy_id}.json`);
502
+ console.log(`šŸ” Health check: http://${host}:${port}/health`);
503
+ if (state.policyDir) {
504
+ console.log(`šŸ“ Serving policies from: ${state.policyDir}`);
505
+ console.log(`šŸ“ Policy files should be named: policy-{id}.yaml or policy-{id}.json`);
506
+ }
507
+ else {
508
+ console.log(`āš ļø No policy directory set (set FAME_POLICY_DIR to serve custom policies)`);
509
+ }
510
+ console.log(`šŸ“Š Loaded ${state.policies.size} policy(ies): ${Array.from(state.policies.keys()).join(", ")}`);
511
+ if (issuer) {
512
+ console.log(`šŸ” OAuth2 JWT authentication enabled (issuer: ${issuer})`);
513
+ }
514
+ else {
515
+ console.log(`āš ļø No authentication (set FAME_OAUTH2_ISSUER to enable)`);
516
+ }
517
+ console.log("");
518
+ }
519
+ catch (error) {
520
+ logger.error("auth_policy_server_startup_failed", {
521
+ error: error instanceof Error ? error.message : String(error),
522
+ });
523
+ process.exit(1);
524
+ }
525
+ }
526
+ export { createApp, main, loadPoliciesFromDir, loadPolicyFile, extractPolicyId, computeEtag, DEFAULT_POLICY, };
527
+ const isTopLevelInvocation = (() => {
528
+ if (typeof process === "undefined") {
529
+ return false;
530
+ }
531
+ const entry = process.argv[1] ?? null;
532
+ if (!entry) {
533
+ return false;
534
+ }
535
+ try {
536
+ const entryPath = resolveToRealPath(entry);
537
+ if (!entryPath) {
538
+ return false;
539
+ }
540
+ return /(?:^|[\\/])auth-policy-server\.js$/u.test(entryPath);
541
+ }
542
+ catch {
543
+ return false;
544
+ }
545
+ })();
546
+ if (isTopLevelInvocation) {
547
+ void main();
548
+ }
549
+ function resolveToRealPath(pathLike) {
550
+ try {
551
+ return realpathSync(pathLike);
552
+ }
553
+ catch {
554
+ try {
555
+ return realpathSync.native?.(pathLike) ?? resolve(pathLike);
556
+ }
557
+ catch {
558
+ return resolve(pathLike);
559
+ }
560
+ }
561
+ }
562
+ //# sourceMappingURL=auth-policy-server.js.map