@galaxy-stack/orbit-graphql-federation 0.1.7 → 0.1.8

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 (2) hide show
  1. package/dist/index.js +293 -7
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -446,6 +446,269 @@ class SchemaBuilder {
446
446
  // ../graphql/src/module/graphql.module.ts
447
447
  import"reflect-metadata";
448
448
  import { parse, validate, execute } from "graphql";
449
+
450
+ // ../common/src/decorators/params.decorator.ts
451
+ import"reflect-metadata";
452
+ var ROUTE_PARAMS_KEY = "orbit:route:params";
453
+ function createParamDecorator2(type) {
454
+ return (data) => {
455
+ return (target, propertyKey, parameterIndex) => {
456
+ if (!propertyKey)
457
+ return;
458
+ const existingParams = Reflect.getMetadata(ROUTE_PARAMS_KEY, target, propertyKey) || [];
459
+ existingParams.push({
460
+ type,
461
+ data,
462
+ index: parameterIndex
463
+ });
464
+ Reflect.defineMetadata(ROUTE_PARAMS_KEY, existingParams, target, propertyKey);
465
+ };
466
+ };
467
+ }
468
+ var Body = createParamDecorator2("body" /* BODY */);
469
+ var Query = createParamDecorator2("query" /* QUERY */);
470
+ var Param = createParamDecorator2("param" /* PARAM */);
471
+ var Headers2 = createParamDecorator2("headers" /* HEADERS */);
472
+ var Req = createParamDecorator2("request" /* REQUEST */);
473
+ var Res = createParamDecorator2("response" /* RESPONSE */);
474
+ var Ip = createParamDecorator2("ip" /* IP */);
475
+ var Session = createParamDecorator2("session" /* SESSION */);
476
+ var UploadedFile = createParamDecorator2("file" /* FILE */);
477
+ var UploadedFiles = createParamDecorator2("files" /* FILES */);
478
+ // ../common/src/decorators/http-code.decorator.ts
479
+ import"reflect-metadata";
480
+ // ../common/src/decorators/use-guards.decorator.ts
481
+ import"reflect-metadata";
482
+ // ../common/src/decorators/use-pipes.decorator.ts
483
+ import"reflect-metadata";
484
+ // ../common/src/decorators/use-interceptors.decorator.ts
485
+ import"reflect-metadata";
486
+ // ../common/src/decorators/catch.decorator.ts
487
+ import"reflect-metadata";
488
+ // ../common/src/decorators/transform.decorator.ts
489
+ var TRANSFORM_METADATA = Symbol("transform:metadata");
490
+ // ../common/src/security-headers.ts
491
+ var DEFAULTS = {
492
+ hsts: true,
493
+ frameguard: "SAMEORIGIN",
494
+ noSniff: true,
495
+ referrerPolicy: "no-referrer",
496
+ crossOriginOpenerPolicy: "same-origin",
497
+ crossOriginResourcePolicy: "same-origin",
498
+ hidePoweredBy: true,
499
+ originAgentCluster: true,
500
+ permittedCrossDomainPolicies: "none",
501
+ dnsPrefetchControl: false
502
+ };
503
+ function buildSecureHeaders(options = {}) {
504
+ const opts = { ...DEFAULTS, ...options };
505
+ const headers = {};
506
+ if (opts.hidePoweredBy) {
507
+ headers["X-Powered-By"] = "";
508
+ }
509
+ if (opts.noSniff) {
510
+ headers["X-Content-Type-Options"] = "nosniff";
511
+ }
512
+ if (opts.frameguard) {
513
+ headers["X-Frame-Options"] = opts.frameguard;
514
+ }
515
+ if (opts.hsts) {
516
+ const hsts = typeof opts.hsts === "object" ? opts.hsts : { maxAge: 15552000, includeSubDomains: true };
517
+ let value = `max-age=${hsts.maxAge ?? 15552000}`;
518
+ if (hsts.includeSubDomains !== false)
519
+ value += "; includeSubDomains";
520
+ if ("preload" in hsts && hsts.preload)
521
+ value += "; preload";
522
+ headers["Strict-Transport-Security"] = value;
523
+ }
524
+ if (opts.referrerPolicy) {
525
+ headers["Referrer-Policy"] = opts.referrerPolicy;
526
+ }
527
+ if (opts.crossOriginOpenerPolicy) {
528
+ headers["Cross-Origin-Opener-Policy"] = opts.crossOriginOpenerPolicy;
529
+ }
530
+ if (opts.crossOriginResourcePolicy) {
531
+ headers["Cross-Origin-Resource-Policy"] = opts.crossOriginResourcePolicy;
532
+ }
533
+ if (opts.originAgentCluster) {
534
+ headers["Origin-Agent-Cluster"] = "?1";
535
+ }
536
+ if (opts.permittedCrossDomainPolicies) {
537
+ headers["X-Permitted-Cross-Domain-Policies"] = opts.permittedCrossDomainPolicies;
538
+ }
539
+ if (opts.dnsPrefetchControl) {
540
+ headers["X-DNS-Prefetch-Control"] = "on";
541
+ } else {
542
+ headers["X-DNS-Prefetch-Control"] = "off";
543
+ }
544
+ return headers;
545
+ }
546
+ function withSecureHeaders(response, options = {}) {
547
+ const secure = buildSecureHeaders(options);
548
+ const headers = new Headers(response.headers);
549
+ for (const [key, value] of Object.entries(secure)) {
550
+ if (value === "") {
551
+ headers.delete(key);
552
+ } else {
553
+ headers.set(key, value);
554
+ }
555
+ }
556
+ return new Response(response.body, {
557
+ status: response.status,
558
+ statusText: response.statusText,
559
+ headers
560
+ });
561
+ }
562
+ // ../graphql/src/security/validation-rules.ts
563
+ import {
564
+ GraphQLError,
565
+ Kind
566
+ } from "graphql";
567
+ function depthLimit(maxDepth) {
568
+ return (context) => {
569
+ const definedFragments = new Map;
570
+ for (const def of context.getDocument().definitions) {
571
+ if (def.kind === Kind.FRAGMENT_DEFINITION) {
572
+ definedFragments.set(def.name.value, def);
573
+ }
574
+ }
575
+ const fragmentDepths = new Map;
576
+ const visiting = new Set;
577
+ const measureSelections = (selections, depth) => {
578
+ let deepest = depth;
579
+ for (const node of selections) {
580
+ if (node.kind === Kind.FIELD) {
581
+ const field = node;
582
+ if (field.name.value === "__typename")
583
+ continue;
584
+ deepest = Math.max(deepest, measureSelections(field.selectionSet?.selections ?? [], depth + 1));
585
+ } else if (node.kind === Kind.INLINE_FRAGMENT) {
586
+ deepest = Math.max(deepest, measureSelections(node.selectionSet.selections, depth));
587
+ } else if (node.kind === Kind.FRAGMENT_SPREAD) {
588
+ const name = node.name.value;
589
+ if (visiting.has(name))
590
+ continue;
591
+ const frag = definedFragments.get(name);
592
+ if (!frag)
593
+ continue;
594
+ const cached = fragmentDepths.get(name);
595
+ const fragDepth = cached !== undefined ? cached : (() => {
596
+ visiting.add(name);
597
+ const d = measureSelections(frag.selectionSet.selections, depth);
598
+ visiting.delete(name);
599
+ fragmentDepths.set(name, d);
600
+ return d;
601
+ })();
602
+ deepest = Math.max(deepest, fragDepth);
603
+ }
604
+ }
605
+ return deepest;
606
+ };
607
+ return {
608
+ OperationDefinition(node) {
609
+ const depth = measureSelections(node.selectionSet.selections, 0);
610
+ if (depth > maxDepth) {
611
+ context.reportError(new GraphQLError(`Query exceeds maximum depth of ${maxDepth} (got ${depth}).`, { nodes: node }));
612
+ }
613
+ }
614
+ };
615
+ };
616
+ }
617
+ function complexityLimit(options) {
618
+ const {
619
+ maxComplexity,
620
+ defaultFieldCost = 1,
621
+ defaultListFactor = 10,
622
+ fieldCosts = {},
623
+ listFactors = {}
624
+ } = options;
625
+ return (context) => {
626
+ const definedFragments = new Map;
627
+ for (const def of context.getDocument().definitions) {
628
+ if (def.kind === Kind.FRAGMENT_DEFINITION) {
629
+ definedFragments.set(def.name.value, def);
630
+ }
631
+ }
632
+ const fragmentCosts = new Map;
633
+ const visiting = new Set;
634
+ const measureSelections = (selections, multiplier) => {
635
+ let total = 0;
636
+ for (const node of selections) {
637
+ if (node.kind === Kind.FIELD) {
638
+ const field = node;
639
+ if (field.name.value === "__typename")
640
+ continue;
641
+ const cost = fieldCosts[field.name.value] ?? defaultFieldCost;
642
+ const isList = Boolean(field.selectionSet) && (listFactors[field.name.value] ?? defaultListFactor);
643
+ const factor = field.selectionSet ? listFactors[field.name.value] ?? defaultListFactor : 1;
644
+ const childCost = field.selectionSet ? measureSelections(field.selectionSet.selections, multiplier * factor) : 0;
645
+ total += cost * multiplier + childCost;
646
+ } else if (node.kind === Kind.INLINE_FRAGMENT) {
647
+ total += measureSelections(node.selectionSet.selections, multiplier);
648
+ } else if (node.kind === Kind.FRAGMENT_SPREAD) {
649
+ const name = node.name.value;
650
+ if (visiting.has(name))
651
+ continue;
652
+ const frag = definedFragments.get(name);
653
+ if (!frag)
654
+ continue;
655
+ const cached = fragmentCosts.get(name);
656
+ const fragCost = cached !== undefined ? cached : (() => {
657
+ visiting.add(name);
658
+ const c = measureSelections(frag.selectionSet.selections, multiplier);
659
+ visiting.delete(name);
660
+ fragmentCosts.set(name, c);
661
+ return c;
662
+ })();
663
+ total += fragCost;
664
+ }
665
+ }
666
+ return total;
667
+ };
668
+ return {
669
+ OperationDefinition(node) {
670
+ const complexity = measureSelections(node.selectionSet.selections, 1);
671
+ if (complexity > maxComplexity) {
672
+ context.reportError(new GraphQLError(`Query complexity ${complexity} exceeds maximum of ${maxComplexity}.`, { nodes: node }));
673
+ }
674
+ }
675
+ };
676
+ };
677
+ }
678
+ function aliasLimit(maxAliases) {
679
+ return (context) => {
680
+ let aliasCount = 0;
681
+ return {
682
+ Field(node) {
683
+ if (node.alias) {
684
+ aliasCount++;
685
+ if (aliasCount > maxAliases) {
686
+ context.reportError(new GraphQLError(`Query exceeds maximum of ${maxAliases} aliases.`, { nodes: node }));
687
+ }
688
+ }
689
+ }
690
+ };
691
+ };
692
+ }
693
+ function blockIntrospection() {
694
+ return (context) => {
695
+ return {
696
+ Field(node) {
697
+ if (node.name.value === "__schema" || node.name.value === "__type") {
698
+ context.reportError(new GraphQLError("GraphQL introspection is disabled.", { nodes: node }));
699
+ }
700
+ }
701
+ };
702
+ };
703
+ }
704
+ // ../graphql/src/security/options.ts
705
+ var DEFAULT_GRAPHQL_SECURITY = {
706
+ maxDepth: 10,
707
+ maxComplexity: 1000,
708
+ maxAliases: 30
709
+ };
710
+
711
+ // ../graphql/src/module/graphql.module.ts
449
712
  var GRAPHQL_OPTIONS = Symbol("GRAPHQL_OPTIONS");
450
713
  var GRAPHQL_SCHEMA = Symbol("GRAPHQL_SCHEMA");
451
714
 
@@ -530,6 +793,11 @@ class GraphQLHandler {
530
793
  this.schema = schema;
531
794
  this.options = options;
532
795
  }
796
+ wrap(response) {
797
+ if (this.options.secureHeaders === false)
798
+ return response;
799
+ return withSecureHeaders(response);
800
+ }
533
801
  createLoaderContext() {
534
802
  const ctx = new DataLoaderContext;
535
803
  const loaders = this.options.loaders || {};
@@ -561,14 +829,14 @@ class GraphQLHandler {
561
829
  ...userContext
562
830
  };
563
831
  const document = parse(query);
564
- const validationErrors = validate(this.schema, document);
832
+ const validationErrors = validate(this.schema, document, this.buildSecurityRules());
565
833
  if (validationErrors.length > 0) {
566
- return new Response(JSON.stringify({
834
+ return this.wrap(new Response(JSON.stringify({
567
835
  errors: validationErrors.map((e) => this.formatError(e))
568
836
  }), {
569
837
  status: 400,
570
838
  headers: { "Content-Type": "application/json" }
571
- });
839
+ }));
572
840
  }
573
841
  const result = await execute({
574
842
  schema: this.schema,
@@ -580,17 +848,35 @@ class GraphQLHandler {
580
848
  if (result.errors) {
581
849
  result.errors = result.errors.map((e) => this.formatError(e));
582
850
  }
583
- return new Response(JSON.stringify(result), {
851
+ return this.wrap(new Response(JSON.stringify(result), {
584
852
  headers: { "Content-Type": "application/json" }
585
- });
853
+ }));
586
854
  } catch (error) {
587
- return new Response(JSON.stringify({
855
+ return this.wrap(new Response(JSON.stringify({
588
856
  errors: [this.formatError(error)]
589
857
  }), {
590
858
  status: 500,
591
859
  headers: { "Content-Type": "application/json" }
592
- });
860
+ }));
861
+ }
862
+ }
863
+ buildSecurityRules() {
864
+ const sec = this.options.security || {};
865
+ const introspection = this.options.introspection !== false;
866
+ const disableIntrospection = sec.disableIntrospection ?? !introspection;
867
+ const rules = [
868
+ depthLimit(sec.maxDepth ?? DEFAULT_GRAPHQL_SECURITY.maxDepth),
869
+ complexityLimit({
870
+ maxComplexity: sec.maxComplexity ?? DEFAULT_GRAPHQL_SECURITY.maxComplexity,
871
+ fieldCosts: sec.fieldCosts,
872
+ listFactors: sec.listFactors
873
+ }),
874
+ aliasLimit(sec.maxAliases ?? DEFAULT_GRAPHQL_SECURITY.maxAliases)
875
+ ];
876
+ if (disableIntrospection) {
877
+ rules.push(blockIntrospection());
593
878
  }
879
+ return rules;
594
880
  }
595
881
  formatError(error) {
596
882
  if (this.options.formatError) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@galaxy-stack/orbit-graphql-federation",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "GraphQL Federation support for Orbit framework",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",