@conduitplatform/module-tools 0.17.0-alpha.7 → 0.17.0-alpha.9
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.
- package/README.mdx +13 -0
- package/dist/index.cjs +678 -127
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +185 -4
- package/dist/index.d.ts +185 -4
- package/dist/index.esm.js +639 -96
- package/dist/index.esm.js.map +1 -1
- package/package.json +9 -8
package/dist/index.esm.js
CHANGED
|
@@ -402,9 +402,9 @@ var GrpcServer = class {
|
|
|
402
402
|
import path2 from "path";
|
|
403
403
|
import { EventEmitter } from "events";
|
|
404
404
|
import { camelCase } from "lodash-es";
|
|
405
|
-
import { status as
|
|
405
|
+
import { status as status5 } from "@grpc/grpc-js";
|
|
406
406
|
import {
|
|
407
|
-
ConduitGrpcSdk as
|
|
407
|
+
ConduitGrpcSdk as ConduitGrpcSdk6,
|
|
408
408
|
HealthCheckResponse_ServingStatus,
|
|
409
409
|
HealthCheckStatus
|
|
410
410
|
} from "@conduitplatform/grpc-sdk";
|
|
@@ -487,6 +487,16 @@ var RouteBuilder = class {
|
|
|
487
487
|
this._options.captureRawHeaders = capture;
|
|
488
488
|
return this;
|
|
489
489
|
}
|
|
490
|
+
/**
|
|
491
|
+
* Whether module-tools hydrates `context.user` from `context.userId` on routes with
|
|
492
|
+
* `authMiddleware` / `authMiddleware?` (default true).
|
|
493
|
+
* @deprecated The default flips to false in the next major; call
|
|
494
|
+
* `grpcSdk.authentication.getUser(userId)` explicitly instead.
|
|
495
|
+
*/
|
|
496
|
+
hydrateUser(hydrate) {
|
|
497
|
+
this._options.hydrateUser = hydrate;
|
|
498
|
+
return this;
|
|
499
|
+
}
|
|
490
500
|
return(name, fields) {
|
|
491
501
|
this._returns = new ConduitRouteReturnDefinition(name, fields);
|
|
492
502
|
return this;
|
|
@@ -520,45 +530,105 @@ var RouteBuilder = class {
|
|
|
520
530
|
|
|
521
531
|
// src/routing/wrapRouterFunctions.ts
|
|
522
532
|
import {
|
|
523
|
-
ConduitGrpcSdk as
|
|
533
|
+
ConduitGrpcSdk as ConduitGrpcSdk4
|
|
524
534
|
} from "@conduitplatform/grpc-sdk";
|
|
535
|
+
import { status as status3 } from "@grpc/grpc-js";
|
|
536
|
+
|
|
537
|
+
// src/routing/hydrateUser.ts
|
|
538
|
+
import { ConduitGrpcSdk as ConduitGrpcSdk3, GrpcError as GrpcError2 } from "@conduitplatform/grpc-sdk";
|
|
525
539
|
import { status as status2 } from "@grpc/grpc-js";
|
|
540
|
+
function middlewareList(routeOptions) {
|
|
541
|
+
const m = routeOptions?.middlewares;
|
|
542
|
+
if (Array.isArray(m)) return m.filter((x) => typeof x === "string");
|
|
543
|
+
if (typeof m === "string") return [m];
|
|
544
|
+
return [];
|
|
545
|
+
}
|
|
546
|
+
function getAuthMiddlewareKind(routeOptions) {
|
|
547
|
+
const list = middlewareList(routeOptions);
|
|
548
|
+
if (list.includes("authMiddleware")) return "required";
|
|
549
|
+
if (list.includes("authMiddleware?")) return "optional";
|
|
550
|
+
return void 0;
|
|
551
|
+
}
|
|
552
|
+
function shouldHydrateUser(routeOptions, context) {
|
|
553
|
+
if (!routeOptions || !context) return false;
|
|
554
|
+
if (routeOptions.hydrateUser === false) return false;
|
|
555
|
+
if (!getAuthMiddlewareKind(routeOptions)) return false;
|
|
556
|
+
return typeof context.userId === "string" && !!context.userId && !context.user;
|
|
557
|
+
}
|
|
558
|
+
function createUserHydrator(routeOptions, resolve) {
|
|
559
|
+
if (!routeOptions) return void 0;
|
|
560
|
+
const kind = getAuthMiddlewareKind(routeOptions);
|
|
561
|
+
if (!kind || routeOptions.hydrateUser === false) return void 0;
|
|
562
|
+
return {
|
|
563
|
+
optional: kind === "optional",
|
|
564
|
+
getUser: async (userId) => {
|
|
565
|
+
const resolver = resolve();
|
|
566
|
+
if (!resolver) throw new GrpcError2(status2.UNAVAILABLE, "User lookup unavailable");
|
|
567
|
+
return resolver(userId);
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
function mapHydrationErrorCode(error) {
|
|
572
|
+
const code = error?.code;
|
|
573
|
+
if (typeof code !== "number" || code === status2.OK) return status2.INTERNAL;
|
|
574
|
+
if (code === status2.NOT_FOUND) return status2.UNAUTHENTICATED;
|
|
575
|
+
return code;
|
|
576
|
+
}
|
|
577
|
+
async function hydrateContextUser(routeOptions, context, hydrator) {
|
|
578
|
+
if (!shouldHydrateUser(routeOptions, context)) return;
|
|
579
|
+
try {
|
|
580
|
+
context.user = await hydrator.getUser(context.userId);
|
|
581
|
+
} catch (e) {
|
|
582
|
+
ConduitGrpcSdk3.Logger.warn(
|
|
583
|
+
`User hydration failed: ${e?.message ?? String(e)}`
|
|
584
|
+
);
|
|
585
|
+
if (!hydrator.optional) {
|
|
586
|
+
const code = mapHydrationErrorCode(e);
|
|
587
|
+
throw new GrpcError2(
|
|
588
|
+
code,
|
|
589
|
+
code === status2.UNAUTHENTICATED ? "Unauthorized" : "User lookup failed"
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// src/routing/wrapRouterFunctions.ts
|
|
526
596
|
var mapGrpcStatusToHttp = (code) => {
|
|
527
597
|
if (!code) return "200 (OK)";
|
|
528
598
|
switch (code) {
|
|
529
|
-
case
|
|
599
|
+
case status3.OK:
|
|
530
600
|
return `200 (OK)`;
|
|
531
|
-
case
|
|
601
|
+
case status3.CANCELLED:
|
|
532
602
|
return `499 (Cancelled)`;
|
|
533
|
-
case
|
|
603
|
+
case status3.INVALID_ARGUMENT:
|
|
534
604
|
return `400 (Invalid Argument)`;
|
|
535
|
-
case
|
|
605
|
+
case status3.DEADLINE_EXCEEDED:
|
|
536
606
|
return `504 (Deadline Exceeded)`;
|
|
537
|
-
case
|
|
607
|
+
case status3.NOT_FOUND:
|
|
538
608
|
return `404 (Not Found)`;
|
|
539
|
-
case
|
|
609
|
+
case status3.ALREADY_EXISTS:
|
|
540
610
|
return `409 (Already Exists)`;
|
|
541
|
-
case
|
|
611
|
+
case status3.PERMISSION_DENIED:
|
|
542
612
|
return `403 (Permission Denied)`;
|
|
543
|
-
case
|
|
613
|
+
case status3.UNAUTHENTICATED:
|
|
544
614
|
return `401 (Unauthenticated)`;
|
|
545
|
-
case
|
|
615
|
+
case status3.OUT_OF_RANGE:
|
|
546
616
|
return `416 (Out of Range)`;
|
|
547
|
-
case
|
|
617
|
+
case status3.FAILED_PRECONDITION:
|
|
548
618
|
return `412 (Failed Precondition)`;
|
|
549
|
-
case
|
|
619
|
+
case status3.ABORTED:
|
|
550
620
|
return `409 (Aborted)`;
|
|
551
|
-
case
|
|
621
|
+
case status3.INTERNAL:
|
|
552
622
|
return `416 (Internal)`;
|
|
553
|
-
case
|
|
623
|
+
case status3.UNAVAILABLE:
|
|
554
624
|
return `503 (Service Unavailable)`;
|
|
555
|
-
case
|
|
625
|
+
case status3.DATA_LOSS:
|
|
556
626
|
return `500 (Data Loss)`;
|
|
557
627
|
default:
|
|
558
628
|
return `500 (Unknown)`;
|
|
559
629
|
}
|
|
560
630
|
};
|
|
561
|
-
function generateLog(routerType, routerRequest, requestReceive, call,
|
|
631
|
+
function generateLog(routerType, routerRequest, requestReceive, call, status10) {
|
|
562
632
|
let log = "";
|
|
563
633
|
const latency = Date.now() - requestReceive;
|
|
564
634
|
if (routerRequest) {
|
|
@@ -568,15 +638,15 @@ function generateLog(routerType, routerRequest, requestReceive, call, status6) {
|
|
|
568
638
|
const socketLabel = socketReq.socketId ?? socketReq.socket;
|
|
569
639
|
log += `(${routerType}) Socket: ${socketReq.event} socket: ${socketLabel}`;
|
|
570
640
|
}
|
|
571
|
-
log += ` ${mapGrpcStatusToHttp(
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
641
|
+
log += ` ${mapGrpcStatusToHttp(status10) ?? "200"} ${latency}ms`;
|
|
642
|
+
ConduitGrpcSdk4.Logger.log(log);
|
|
643
|
+
ConduitGrpcSdk4.Metrics?.set("grpc_request_latency_seconds", latency / 1e3);
|
|
644
|
+
ConduitGrpcSdk4.Metrics?.set(
|
|
575
645
|
`${routerType}_grpc_request_latency_seconds`,
|
|
576
646
|
latency / 1e3
|
|
577
647
|
);
|
|
578
|
-
const successStatus = !
|
|
579
|
-
|
|
648
|
+
const successStatus = !status10 || status10.toString().startsWith("2");
|
|
649
|
+
ConduitGrpcSdk4.Metrics?.increment(`${routerType}_grpc_response_statuses_total`, 1, {
|
|
580
650
|
success: successStatus ? "true" : "false"
|
|
581
651
|
});
|
|
582
652
|
}
|
|
@@ -592,7 +662,7 @@ function parseResponseData(r, routerRequest, requestReceive, call, callback, res
|
|
|
592
662
|
responded.did = true;
|
|
593
663
|
if (!r) {
|
|
594
664
|
callback({
|
|
595
|
-
code:
|
|
665
|
+
code: status3.INTERNAL,
|
|
596
666
|
message: "Handler did not return a response"
|
|
597
667
|
});
|
|
598
668
|
}
|
|
@@ -623,11 +693,11 @@ function parseResponseData(r, routerRequest, requestReceive, call, callback, res
|
|
|
623
693
|
}
|
|
624
694
|
generateLog(routerType, routerRequest, requestReceive, call, void 0);
|
|
625
695
|
}
|
|
626
|
-
function wrapRouterGrpcFunction(fun, routerType) {
|
|
696
|
+
function wrapRouterGrpcFunction(fun, routerType, hydration) {
|
|
627
697
|
return (call, callback) => {
|
|
628
698
|
const requestReceive = Date.now();
|
|
629
699
|
let routerRequest = true;
|
|
630
|
-
|
|
700
|
+
ConduitGrpcSdk4.Metrics?.increment(`${routerType}_grpc_requests_total`);
|
|
631
701
|
try {
|
|
632
702
|
call.request.context = parseRequestData(call.request.context);
|
|
633
703
|
call.request.params = parseRequestData(call.request.params);
|
|
@@ -646,16 +716,16 @@ function wrapRouterGrpcFunction(fun, routerType) {
|
|
|
646
716
|
}
|
|
647
717
|
}
|
|
648
718
|
} catch (e) {
|
|
649
|
-
|
|
650
|
-
generateLog(routerType, routerRequest, requestReceive, call,
|
|
651
|
-
|
|
719
|
+
ConduitGrpcSdk4.Metrics?.increment(`${routerType}_grpc_errors_total`);
|
|
720
|
+
generateLog(routerType, routerRequest, requestReceive, call, status3.INTERNAL);
|
|
721
|
+
ConduitGrpcSdk4.Logger.error(e.message ?? "Something went wrong");
|
|
652
722
|
return callback({
|
|
653
|
-
code:
|
|
723
|
+
code: status3.INTERNAL,
|
|
654
724
|
message: e.message ?? "Something went wrong"
|
|
655
725
|
});
|
|
656
726
|
}
|
|
657
727
|
const responded = { did: false };
|
|
658
|
-
fun(call, (r) => {
|
|
728
|
+
const invoke = () => fun(call, (r) => {
|
|
659
729
|
parseResponseData(
|
|
660
730
|
r,
|
|
661
731
|
routerRequest,
|
|
@@ -665,7 +735,13 @@ function wrapRouterGrpcFunction(fun, routerType) {
|
|
|
665
735
|
responded,
|
|
666
736
|
routerType
|
|
667
737
|
);
|
|
668
|
-
})
|
|
738
|
+
});
|
|
739
|
+
const needsHydration = hydration && shouldHydrateUser(hydration.options, call.request.context);
|
|
740
|
+
(needsHydration ? hydrateContextUser(
|
|
741
|
+
hydration.options,
|
|
742
|
+
call.request.context,
|
|
743
|
+
hydration.hydrator
|
|
744
|
+
).then(invoke) : invoke()).then(
|
|
669
745
|
(r) => parseResponseData(
|
|
670
746
|
r,
|
|
671
747
|
routerRequest,
|
|
@@ -676,19 +752,19 @@ function wrapRouterGrpcFunction(fun, routerType) {
|
|
|
676
752
|
routerType
|
|
677
753
|
)
|
|
678
754
|
).catch((error) => {
|
|
679
|
-
|
|
755
|
+
ConduitGrpcSdk4.Metrics?.increment(`${routerType}_grpc_errors_total`);
|
|
680
756
|
generateLog(
|
|
681
757
|
routerType,
|
|
682
758
|
routerRequest,
|
|
683
759
|
requestReceive,
|
|
684
760
|
call,
|
|
685
|
-
error.code ??
|
|
761
|
+
error.code ?? status3.INTERNAL
|
|
686
762
|
);
|
|
687
|
-
|
|
763
|
+
ConduitGrpcSdk4.Logger.error(
|
|
688
764
|
error.debugLogInfo ?? error.message ?? "Something went wrong"
|
|
689
765
|
);
|
|
690
766
|
callback({
|
|
691
|
-
code: error.code ??
|
|
767
|
+
code: error.code ?? status3.INTERNAL,
|
|
692
768
|
message: error.details ?? error.message ?? "Something went wrong"
|
|
693
769
|
});
|
|
694
770
|
});
|
|
@@ -696,10 +772,14 @@ function wrapRouterGrpcFunction(fun, routerType) {
|
|
|
696
772
|
}
|
|
697
773
|
|
|
698
774
|
// src/routing/RoutingUtilities.ts
|
|
699
|
-
function wrapFunctionsAsync(functions, routerType) {
|
|
775
|
+
function wrapFunctionsAsync(functions, routerType, hydrations = {}) {
|
|
700
776
|
const modifiedFunctions = {};
|
|
701
777
|
Object.keys(functions).forEach((key) => {
|
|
702
|
-
modifiedFunctions[key] = wrapRouterGrpcFunction(
|
|
778
|
+
modifiedFunctions[key] = wrapRouterGrpcFunction(
|
|
779
|
+
functions[key],
|
|
780
|
+
routerType,
|
|
781
|
+
hydrations[key]
|
|
782
|
+
);
|
|
703
783
|
});
|
|
704
784
|
return modifiedFunctions;
|
|
705
785
|
}
|
|
@@ -707,8 +787,11 @@ function wrapFunctionsAsync(functions, routerType) {
|
|
|
707
787
|
// src/routing/RoutingManager.ts
|
|
708
788
|
import {
|
|
709
789
|
Admin,
|
|
710
|
-
ConduitRouteActions as ConduitRouteActions2
|
|
790
|
+
ConduitRouteActions as ConduitRouteActions2,
|
|
791
|
+
ConduitGrpcSdk as ConduitGrpcSdk5,
|
|
792
|
+
GrpcError as GrpcError3
|
|
711
793
|
} from "@conduitplatform/grpc-sdk";
|
|
794
|
+
import { status as status4 } from "@grpc/grpc-js";
|
|
712
795
|
var RoutingManager = class _RoutingManager {
|
|
713
796
|
constructor(_router, _server) {
|
|
714
797
|
this._router = _router;
|
|
@@ -722,6 +805,30 @@ var RoutingManager = class _RoutingManager {
|
|
|
722
805
|
_moduleRoutes = {};
|
|
723
806
|
static ClientController;
|
|
724
807
|
static AdminController;
|
|
808
|
+
/** Set by ManagedModule; used to hydrate context.user on client routes. */
|
|
809
|
+
static grpcSdk;
|
|
810
|
+
static userResolver;
|
|
811
|
+
static warnedNoResolver = false;
|
|
812
|
+
/**
|
|
813
|
+
* Overrides how users are fetched for hydration (default: grpcSdk.authentication.getUser).
|
|
814
|
+
* Intended for the module that owns the users: the authentication module has no gRPC
|
|
815
|
+
* client to itself and will register a local resolver here.
|
|
816
|
+
*/
|
|
817
|
+
static setUserResolver(resolver) {
|
|
818
|
+
_RoutingManager.userResolver = resolver;
|
|
819
|
+
}
|
|
820
|
+
static resolveUser() {
|
|
821
|
+
if (_RoutingManager.userResolver) return _RoutingManager.userResolver;
|
|
822
|
+
const sdk = _RoutingManager.grpcSdk;
|
|
823
|
+
if (!sdk) return void 0;
|
|
824
|
+
return async (userId) => {
|
|
825
|
+
const auth = sdk.authentication;
|
|
826
|
+
if (!auth) {
|
|
827
|
+
throw new GrpcError3(status4.UNAVAILABLE, "authentication module unavailable");
|
|
828
|
+
}
|
|
829
|
+
return auth.getUser(userId);
|
|
830
|
+
};
|
|
831
|
+
}
|
|
725
832
|
_routeHandlers = {};
|
|
726
833
|
isAdmin = false;
|
|
727
834
|
get(path5) {
|
|
@@ -788,7 +895,27 @@ var RoutingManager = class _RoutingManager {
|
|
|
788
895
|
}
|
|
789
896
|
async registerRoutes() {
|
|
790
897
|
if (Object.keys(this._routeHandlers).length === 0) return;
|
|
791
|
-
const
|
|
898
|
+
const hydrations = {};
|
|
899
|
+
if (!this.isAdmin) {
|
|
900
|
+
for (const [name, route] of Object.entries(this._moduleRoutes)) {
|
|
901
|
+
const options = route.options;
|
|
902
|
+
const hydrator = createUserHydrator(options, _RoutingManager.resolveUser);
|
|
903
|
+
if (!hydrator) continue;
|
|
904
|
+
const names = "events" in route ? Object.keys(JSON.parse(route.events || "{}")) : [name];
|
|
905
|
+
for (const n of names) hydrations[n] = { options, hydrator };
|
|
906
|
+
if (!_RoutingManager.grpcSdk && !_RoutingManager.userResolver && !_RoutingManager.warnedNoResolver) {
|
|
907
|
+
_RoutingManager.warnedNoResolver = true;
|
|
908
|
+
ConduitGrpcSdk5.Logger.warn(
|
|
909
|
+
"User hydration is needed but RoutingManager.grpcSdk is not set; hydration will fail"
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
const modifiedFunctions = wrapFunctionsAsync(
|
|
915
|
+
this._routeHandlers,
|
|
916
|
+
this.isAdmin ? "admin" : "client",
|
|
917
|
+
hydrations
|
|
918
|
+
);
|
|
792
919
|
if (this.isAdmin) {
|
|
793
920
|
_RoutingManager.AdminController.setRoutes(modifiedFunctions);
|
|
794
921
|
} else {
|
|
@@ -867,7 +994,7 @@ var ConduitServiceModule = class {
|
|
|
867
994
|
);
|
|
868
995
|
}
|
|
869
996
|
if (!init) {
|
|
870
|
-
|
|
997
|
+
ConduitGrpcSdk6.Metrics?.set(
|
|
871
998
|
"module_health_state",
|
|
872
999
|
state === HealthCheckStatus.SERVING ? 1 : 0
|
|
873
1000
|
);
|
|
@@ -910,9 +1037,9 @@ var ConduitServiceModule = class {
|
|
|
910
1037
|
} else {
|
|
911
1038
|
this.events.on(
|
|
912
1039
|
`grpc-health-change:${this._serviceName}`,
|
|
913
|
-
(
|
|
1040
|
+
(status10) => {
|
|
914
1041
|
call.write({
|
|
915
|
-
status:
|
|
1042
|
+
status: status10
|
|
916
1043
|
});
|
|
917
1044
|
}
|
|
918
1045
|
);
|
|
@@ -935,7 +1062,7 @@ var ConduitServiceModule = class {
|
|
|
935
1062
|
const resources = this.getExportableResources();
|
|
936
1063
|
callback(null, { resources: JSON.stringify(resources) });
|
|
937
1064
|
} catch (e) {
|
|
938
|
-
callback({ code:
|
|
1065
|
+
callback({ code: status5.INTERNAL, message: e.message });
|
|
939
1066
|
}
|
|
940
1067
|
}
|
|
941
1068
|
async exportResourcesHandler(call, callback) {
|
|
@@ -944,7 +1071,7 @@ var ConduitServiceModule = class {
|
|
|
944
1071
|
const data = await this.exportResources(resourceTypes);
|
|
945
1072
|
callback(null, { data: JSON.stringify(data) });
|
|
946
1073
|
} catch (e) {
|
|
947
|
-
callback({ code:
|
|
1074
|
+
callback({ code: status5.INTERNAL, message: e.message });
|
|
948
1075
|
}
|
|
949
1076
|
}
|
|
950
1077
|
async importResourcesHandler(call, callback) {
|
|
@@ -956,11 +1083,11 @@ var ConduitServiceModule = class {
|
|
|
956
1083
|
const err = e;
|
|
957
1084
|
if (err instanceof SyntaxError) {
|
|
958
1085
|
callback({
|
|
959
|
-
code:
|
|
1086
|
+
code: status5.INVALID_ARGUMENT,
|
|
960
1087
|
message: "Invalid JSON in import data"
|
|
961
1088
|
});
|
|
962
1089
|
} else {
|
|
963
|
-
callback({ code:
|
|
1090
|
+
callback({ code: status5.INTERNAL, message: err.message });
|
|
964
1091
|
}
|
|
965
1092
|
}
|
|
966
1093
|
}
|
|
@@ -1033,7 +1160,7 @@ var ConfigController = class _ConfigController {
|
|
|
1033
1160
|
|
|
1034
1161
|
// src/ManagedModule.ts
|
|
1035
1162
|
import { kebabCase } from "lodash-es";
|
|
1036
|
-
import { status as
|
|
1163
|
+
import { status as status9 } from "@grpc/grpc-js";
|
|
1037
1164
|
import convict from "convict";
|
|
1038
1165
|
|
|
1039
1166
|
// src/interfaces/ModuleLifecycleStage.ts
|
|
@@ -1048,18 +1175,18 @@ var ModuleLifecycleStage = /* @__PURE__ */ ((ModuleLifecycleStage2) => {
|
|
|
1048
1175
|
|
|
1049
1176
|
// src/ManagedModule.ts
|
|
1050
1177
|
import {
|
|
1051
|
-
ConduitGrpcSdk as
|
|
1178
|
+
ConduitGrpcSdk as ConduitGrpcSdk11
|
|
1052
1179
|
} from "@conduitplatform/grpc-sdk";
|
|
1053
1180
|
|
|
1054
1181
|
// src/utilities/initializeSdk.ts
|
|
1055
1182
|
import {
|
|
1056
|
-
ConduitGrpcSdk as
|
|
1183
|
+
ConduitGrpcSdk as ConduitGrpcSdk9,
|
|
1057
1184
|
HealthCheckStatus as HealthCheckStatus2
|
|
1058
1185
|
} from "@conduitplatform/grpc-sdk";
|
|
1059
1186
|
|
|
1060
1187
|
// src/logging/index.ts
|
|
1061
1188
|
import {
|
|
1062
|
-
ConduitGrpcSdk as
|
|
1189
|
+
ConduitGrpcSdk as ConduitGrpcSdk7
|
|
1063
1190
|
} from "@conduitplatform/grpc-sdk";
|
|
1064
1191
|
import winston, { format } from "winston";
|
|
1065
1192
|
import { isEmpty } from "lodash-es";
|
|
@@ -1217,7 +1344,7 @@ async function setupLoki(module, instance) {
|
|
|
1217
1344
|
if (lokiUrl.endsWith("/")) lokiUrl = lokiUrl.slice(0, -1);
|
|
1218
1345
|
const onTry = async () => {
|
|
1219
1346
|
return await lokiReadyCheck(lokiUrl).then(() => {
|
|
1220
|
-
|
|
1347
|
+
ConduitGrpcSdk7.Logger.addTransport(
|
|
1221
1348
|
new LokiTransport({
|
|
1222
1349
|
level: "debug",
|
|
1223
1350
|
host: lokiUrl,
|
|
@@ -1236,15 +1363,90 @@ async function setupLoki(module, instance) {
|
|
|
1236
1363
|
}).catch(() => true);
|
|
1237
1364
|
};
|
|
1238
1365
|
const onFailure = () => {
|
|
1239
|
-
|
|
1366
|
+
ConduitGrpcSdk7.Logger.error(`Failed to connect to Loki on '${lokiUrl}'`);
|
|
1240
1367
|
};
|
|
1241
1368
|
await linearBackoffTimeoutAsync(onTry, 250, 15, onFailure, true);
|
|
1242
1369
|
}
|
|
1243
1370
|
}
|
|
1244
1371
|
|
|
1245
|
-
// src/
|
|
1246
|
-
import
|
|
1372
|
+
// src/logging/fileTransports.ts
|
|
1373
|
+
import fs from "fs";
|
|
1374
|
+
import os from "os";
|
|
1247
1375
|
import path3 from "path";
|
|
1376
|
+
import winston2 from "winston";
|
|
1377
|
+
import DailyRotateFile from "winston-daily-rotate-file";
|
|
1378
|
+
var DEFAULT_RETENTION_DAYS = 3;
|
|
1379
|
+
var DEFAULT_MAX_SIZE = "20m";
|
|
1380
|
+
var SIZE_STRING = /^(?:0\.)?\d+[kmg]$/i;
|
|
1381
|
+
function positiveIntFromEnv(raw, fallback) {
|
|
1382
|
+
if (raw === void 0) return fallback;
|
|
1383
|
+
const trimmed = raw.trim();
|
|
1384
|
+
if (!/^[0-9]+$/.test(trimmed)) return fallback;
|
|
1385
|
+
const value = Number(trimmed);
|
|
1386
|
+
if (!Number.isSafeInteger(value) || value <= 0) return fallback;
|
|
1387
|
+
return value;
|
|
1388
|
+
}
|
|
1389
|
+
function logFileMaxSize() {
|
|
1390
|
+
const raw = process.env.LOG_FILE_MAX_SIZE?.trim();
|
|
1391
|
+
if (!raw) return DEFAULT_MAX_SIZE;
|
|
1392
|
+
if (SIZE_STRING.test(raw)) return raw.toLowerCase();
|
|
1393
|
+
if (/^[0-9]+$/.test(raw)) {
|
|
1394
|
+
const bytes = Number(raw);
|
|
1395
|
+
if (Number.isSafeInteger(bytes) && bytes > 0) return bytes;
|
|
1396
|
+
}
|
|
1397
|
+
return DEFAULT_MAX_SIZE;
|
|
1398
|
+
}
|
|
1399
|
+
function formatMeta(meta) {
|
|
1400
|
+
if (Array.isArray(meta)) {
|
|
1401
|
+
if (meta.length === 0) return "";
|
|
1402
|
+
return meta.map((item) => JSON.stringify(item)).join(" ");
|
|
1403
|
+
}
|
|
1404
|
+
if (meta !== null && typeof meta === "object") {
|
|
1405
|
+
if (Object.keys(meta).length === 0) return "";
|
|
1406
|
+
return JSON.stringify(meta);
|
|
1407
|
+
}
|
|
1408
|
+
return "";
|
|
1409
|
+
}
|
|
1410
|
+
function createFileFormat() {
|
|
1411
|
+
return winston2.format.combine(
|
|
1412
|
+
winston2.format.errors({ stack: true }),
|
|
1413
|
+
winston2.format.timestamp({
|
|
1414
|
+
format: "YYYY-MM-DD HH:mm:ss"
|
|
1415
|
+
}),
|
|
1416
|
+
winston2.format.printf((info) => {
|
|
1417
|
+
const metaText = formatMeta(info.meta);
|
|
1418
|
+
const line = `[${info.timestamp}] [${info.level.toUpperCase()}]: ${info.message}`;
|
|
1419
|
+
if (info.stack) {
|
|
1420
|
+
return `${line} ${info.stack}`;
|
|
1421
|
+
}
|
|
1422
|
+
return metaText ? `${line} ${metaText}` : line;
|
|
1423
|
+
})
|
|
1424
|
+
);
|
|
1425
|
+
}
|
|
1426
|
+
function rotateTransport(logDir, name, level) {
|
|
1427
|
+
return new DailyRotateFile({
|
|
1428
|
+
dirname: logDir,
|
|
1429
|
+
filename: `${name}-%DATE%.log`,
|
|
1430
|
+
datePattern: "YYYY-MM-DD",
|
|
1431
|
+
level,
|
|
1432
|
+
maxFiles: `${positiveIntFromEnv(process.env.LOG_RETENTION_DAYS, DEFAULT_RETENTION_DAYS)}d`,
|
|
1433
|
+
maxSize: logFileMaxSize(),
|
|
1434
|
+
zippedArchive: false,
|
|
1435
|
+
auditFile: path3.join(logDir, `.${name}-audit.json`),
|
|
1436
|
+
format: createFileFormat()
|
|
1437
|
+
});
|
|
1438
|
+
}
|
|
1439
|
+
function createFileLogTransports() {
|
|
1440
|
+
if (process.env.LOG_TO_FILE?.trim() === "false") {
|
|
1441
|
+
return [];
|
|
1442
|
+
}
|
|
1443
|
+
const logDir = process.env.LOG_FILE_DIR?.trim() || path3.join(os.tmpdir(), "conduit-logs");
|
|
1444
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
1445
|
+
return [
|
|
1446
|
+
rotateTransport(logDir, "combined", "info"),
|
|
1447
|
+
rotateTransport(logDir, "errors", "error")
|
|
1448
|
+
];
|
|
1449
|
+
}
|
|
1248
1450
|
|
|
1249
1451
|
// src/metrics/index.ts
|
|
1250
1452
|
import {
|
|
@@ -1259,7 +1461,7 @@ import {
|
|
|
1259
1461
|
// src/metrics/MetricsServer.ts
|
|
1260
1462
|
import { createServer as createServer2 } from "http";
|
|
1261
1463
|
import { isNaN } from "lodash-es";
|
|
1262
|
-
import { ConduitGrpcSdk as
|
|
1464
|
+
import { ConduitGrpcSdk as ConduitGrpcSdk8 } from "@conduitplatform/grpc-sdk";
|
|
1263
1465
|
var MetricsServer = class {
|
|
1264
1466
|
moduleName;
|
|
1265
1467
|
instance;
|
|
@@ -1274,7 +1476,7 @@ var MetricsServer = class {
|
|
|
1274
1476
|
const url = "0.0.0.0:" + port.toString();
|
|
1275
1477
|
const server = createServer2(this.requestHandler.bind(this));
|
|
1276
1478
|
server.listen(port, () => {
|
|
1277
|
-
|
|
1479
|
+
ConduitGrpcSdk8.Logger.info(`Metrics server listening on: ${url}`);
|
|
1278
1480
|
});
|
|
1279
1481
|
return server;
|
|
1280
1482
|
}
|
|
@@ -1296,12 +1498,12 @@ var MetricsServer = class {
|
|
|
1296
1498
|
getHttpPort() {
|
|
1297
1499
|
const value = process.env["METRICS_PORT"];
|
|
1298
1500
|
if (!value) {
|
|
1299
|
-
|
|
1501
|
+
ConduitGrpcSdk8.Logger.error(`Metrics HTTP port not set`);
|
|
1300
1502
|
process.exit(-1);
|
|
1301
1503
|
}
|
|
1302
1504
|
const port = parseInt(value, 10);
|
|
1303
1505
|
if (isNaN(port) || port < 1 || port > 65535) {
|
|
1304
|
-
|
|
1506
|
+
ConduitGrpcSdk8.Logger.error(`Invalid HTTP port value: ${port}`);
|
|
1305
1507
|
process.exit(-1);
|
|
1306
1508
|
}
|
|
1307
1509
|
return port;
|
|
@@ -1593,7 +1795,7 @@ function clientMiddleware() {
|
|
|
1593
1795
|
clientStartedMetric.inc(labels);
|
|
1594
1796
|
const stopTimer = clientHandlingSecondsMetric.startTimer(labels);
|
|
1595
1797
|
let settled = false;
|
|
1596
|
-
let
|
|
1798
|
+
let status10 = Status.OK;
|
|
1597
1799
|
try {
|
|
1598
1800
|
let request;
|
|
1599
1801
|
if (!call.requestStream) {
|
|
@@ -1619,21 +1821,21 @@ function clientMiddleware() {
|
|
|
1619
1821
|
} catch (err) {
|
|
1620
1822
|
settled = true;
|
|
1621
1823
|
if (err instanceof ClientError) {
|
|
1622
|
-
|
|
1824
|
+
status10 = err.code;
|
|
1623
1825
|
} else if (isAbortError(err)) {
|
|
1624
|
-
|
|
1826
|
+
status10 = Status.ABORTED;
|
|
1625
1827
|
} else {
|
|
1626
|
-
|
|
1828
|
+
status10 = Status.UNKNOWN;
|
|
1627
1829
|
}
|
|
1628
1830
|
throw err;
|
|
1629
1831
|
} finally {
|
|
1630
1832
|
if (!settled) {
|
|
1631
|
-
|
|
1833
|
+
status10 = Status.CANCELLED;
|
|
1632
1834
|
}
|
|
1633
|
-
stopTimer({ [codeLabel]: Status[
|
|
1835
|
+
stopTimer({ [codeLabel]: Status[status10] });
|
|
1634
1836
|
clientHandledMetric.inc({
|
|
1635
1837
|
...labels,
|
|
1636
|
-
[codeLabel]: Status[
|
|
1838
|
+
[codeLabel]: Status[status10]
|
|
1637
1839
|
});
|
|
1638
1840
|
}
|
|
1639
1841
|
};
|
|
@@ -1643,20 +1845,12 @@ function clientMiddleware() {
|
|
|
1643
1845
|
var initializeSdk = (coreUrl, moduleName, watchModules = true, serviceHealthStatusGetter = () => HealthCheckStatus2.SERVING) => {
|
|
1644
1846
|
let logger;
|
|
1645
1847
|
try {
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
level: "info"
|
|
1650
|
-
}),
|
|
1651
|
-
new winston2.transports.File({
|
|
1652
|
-
filename: path3.join(__dirname, ".logs/errors.log"),
|
|
1653
|
-
level: "error"
|
|
1654
|
-
})
|
|
1655
|
-
]);
|
|
1656
|
-
} catch (e) {
|
|
1848
|
+
const fileTransports = createFileLogTransports();
|
|
1849
|
+
logger = fileTransports.length > 0 ? new ConduitLogger(fileTransports) : new ConduitLogger();
|
|
1850
|
+
} catch {
|
|
1657
1851
|
logger = new ConduitLogger();
|
|
1658
1852
|
}
|
|
1659
|
-
const grpcSdk = new
|
|
1853
|
+
const grpcSdk = new ConduitGrpcSdk9(
|
|
1660
1854
|
coreUrl,
|
|
1661
1855
|
moduleName,
|
|
1662
1856
|
watchModules,
|
|
@@ -1665,7 +1859,7 @@ var initializeSdk = (coreUrl, moduleName, watchModules = true, serviceHealthStat
|
|
|
1665
1859
|
);
|
|
1666
1860
|
setupLoki(grpcSdk.name, grpcSdk.instance).then();
|
|
1667
1861
|
if (process.env.METRICS_PORT) {
|
|
1668
|
-
|
|
1862
|
+
ConduitGrpcSdk9.Metrics = new ConduitMetrics(grpcSdk.name, grpcSdk.instance);
|
|
1669
1863
|
grpcSdk.addMiddleware(clientMiddleware());
|
|
1670
1864
|
}
|
|
1671
1865
|
return grpcSdk;
|
|
@@ -1863,8 +2057,346 @@ async function reconcileStoredModuleConfig(args) {
|
|
|
1863
2057
|
return { config: persisted, persisted: true };
|
|
1864
2058
|
}
|
|
1865
2059
|
|
|
2060
|
+
// src/utilities/accessTokenVerifier.ts
|
|
2061
|
+
import { ConduitGrpcSdk as ConduitGrpcSdk10, GrpcError as GrpcError5 } from "@conduitplatform/grpc-sdk";
|
|
2062
|
+
import { status as status7 } from "@grpc/grpc-js";
|
|
2063
|
+
import { createPublicKey } from "crypto";
|
|
2064
|
+
|
|
2065
|
+
// src/utilities/accessTokenVerifier.logic.ts
|
|
2066
|
+
import { createDecoder, createVerifier as createVerifier2 } from "fast-jwt";
|
|
2067
|
+
import { status as status6 } from "@grpc/grpc-js";
|
|
2068
|
+
import { GrpcError as GrpcError4 } from "@conduitplatform/grpc-sdk";
|
|
2069
|
+
var DEFAULT_LEEWAY_SECONDS = 5;
|
|
2070
|
+
var verifiers = /* @__PURE__ */ new Map();
|
|
2071
|
+
var decode = createDecoder({ complete: true });
|
|
2072
|
+
function fail(reason) {
|
|
2073
|
+
throw new GrpcError4(status6.UNAUTHENTICATED, reason);
|
|
2074
|
+
}
|
|
2075
|
+
function getVerifier(publicKey) {
|
|
2076
|
+
let verifier = verifiers.get(publicKey);
|
|
2077
|
+
if (!verifier) {
|
|
2078
|
+
verifier = createVerifier2({
|
|
2079
|
+
key: publicKey,
|
|
2080
|
+
algorithms: ["EdDSA"],
|
|
2081
|
+
ignoreExpiration: true,
|
|
2082
|
+
ignoreNotBefore: true,
|
|
2083
|
+
cache: false
|
|
2084
|
+
});
|
|
2085
|
+
verifiers.set(publicKey, verifier);
|
|
2086
|
+
}
|
|
2087
|
+
return verifier;
|
|
2088
|
+
}
|
|
2089
|
+
function selectVerificationKey(kid, signing, now) {
|
|
2090
|
+
if (!kid) return void 0;
|
|
2091
|
+
if (signing.publicKey && kid === signing.kid) return signing.publicKey;
|
|
2092
|
+
if (signing.previousKid && signing.previousPublicKey && kid === signing.previousKid && (signing.previousRetiredAt ?? 0) > now) {
|
|
2093
|
+
return signing.previousPublicKey;
|
|
2094
|
+
}
|
|
2095
|
+
return void 0;
|
|
2096
|
+
}
|
|
2097
|
+
function verifyAccessToken(token, state, options = {}) {
|
|
2098
|
+
const now = options.now ?? Date.now();
|
|
2099
|
+
const leeway = options.leewaySeconds ?? DEFAULT_LEEWAY_SECONDS;
|
|
2100
|
+
if (typeof token !== "string" || token.length === 0) fail("malformed token");
|
|
2101
|
+
let kid;
|
|
2102
|
+
try {
|
|
2103
|
+
const decoded = decode(token);
|
|
2104
|
+
if (decoded.header.alg !== "EdDSA") fail("unsupported algorithm");
|
|
2105
|
+
kid = decoded.header.kid;
|
|
2106
|
+
} catch (e) {
|
|
2107
|
+
if (e instanceof GrpcError4) throw e;
|
|
2108
|
+
fail("malformed token");
|
|
2109
|
+
}
|
|
2110
|
+
const key = selectVerificationKey(kid, state.signing, now);
|
|
2111
|
+
if (!key) fail("unknown kid");
|
|
2112
|
+
let claims;
|
|
2113
|
+
try {
|
|
2114
|
+
claims = getVerifier(key)(token);
|
|
2115
|
+
} catch {
|
|
2116
|
+
fail("bad signature");
|
|
2117
|
+
}
|
|
2118
|
+
if (typeof claims.sub !== "string" || typeof claims.sid !== "string" || typeof claims.exp !== "number") {
|
|
2119
|
+
fail("malformed token");
|
|
2120
|
+
}
|
|
2121
|
+
if (now / 1e3 >= claims.exp + leeway) fail("expired");
|
|
2122
|
+
if (claims.tv !== state.tokenVersion) fail("version mismatch");
|
|
2123
|
+
if (options.clientId !== void 0 && claims.cid !== options.clientId) {
|
|
2124
|
+
fail("client mismatch");
|
|
2125
|
+
}
|
|
2126
|
+
return claims;
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
// src/utilities/accessTokenVerifier.ts
|
|
2130
|
+
var AUTHENTICATION_CONFIG_UPDATE_CHANNEL = "authentication:config:update";
|
|
2131
|
+
var INITIAL_RETRY_MS = 1e3;
|
|
2132
|
+
var MAX_RETRY_MS = 3e4;
|
|
2133
|
+
var REFRESH_INTERVAL_MS = 5 * 60 * 1e3;
|
|
2134
|
+
var UNKNOWN_KID_REFETCH_MIN_MS = 10 * 1e3;
|
|
2135
|
+
var SUBSCRIBER_ID = "access-token-verifier";
|
|
2136
|
+
function isConfigWithAccessTokens(config) {
|
|
2137
|
+
return typeof config === "object" && config !== null && typeof config.accessTokens === "object" && config.accessTokens !== null;
|
|
2138
|
+
}
|
|
2139
|
+
var AccessTokenVerifier = class _AccessTokenVerifier {
|
|
2140
|
+
constructor(grpcSdk) {
|
|
2141
|
+
this.grpcSdk = grpcSdk;
|
|
2142
|
+
}
|
|
2143
|
+
grpcSdk;
|
|
2144
|
+
static instance;
|
|
2145
|
+
_mode = "session";
|
|
2146
|
+
_strictRevocation = false;
|
|
2147
|
+
_tokenVersion = 0;
|
|
2148
|
+
_signing;
|
|
2149
|
+
_loaded = false;
|
|
2150
|
+
_subscribed = false;
|
|
2151
|
+
_generation = 0;
|
|
2152
|
+
_running = false;
|
|
2153
|
+
_retryTimer;
|
|
2154
|
+
_refreshTimer;
|
|
2155
|
+
_lastRefetch = 0;
|
|
2156
|
+
_refetching = false;
|
|
2157
|
+
_warned = /* @__PURE__ */ new Set();
|
|
2158
|
+
_listeners = /* @__PURE__ */ new Set();
|
|
2159
|
+
static getInstance(grpcSdk) {
|
|
2160
|
+
if (!_AccessTokenVerifier.instance) {
|
|
2161
|
+
_AccessTokenVerifier.instance = new _AccessTokenVerifier(grpcSdk);
|
|
2162
|
+
}
|
|
2163
|
+
return _AccessTokenVerifier.instance;
|
|
2164
|
+
}
|
|
2165
|
+
/**
|
|
2166
|
+
* Internal, for tests only: stops the verifier, clears all state and drops the singleton.
|
|
2167
|
+
*/
|
|
2168
|
+
static resetForTests() {
|
|
2169
|
+
_AccessTokenVerifier.instance?.stop();
|
|
2170
|
+
_AccessTokenVerifier.instance?.resetState();
|
|
2171
|
+
_AccessTokenVerifier.instance = void 0;
|
|
2172
|
+
}
|
|
2173
|
+
resetState() {
|
|
2174
|
+
this._mode = "session";
|
|
2175
|
+
this._strictRevocation = false;
|
|
2176
|
+
this._tokenVersion = 0;
|
|
2177
|
+
this._signing = void 0;
|
|
2178
|
+
this._loaded = false;
|
|
2179
|
+
this._lastRefetch = 0;
|
|
2180
|
+
this._warned.clear();
|
|
2181
|
+
this._listeners.clear();
|
|
2182
|
+
}
|
|
2183
|
+
/** Snapshot of the verifier's observable state. */
|
|
2184
|
+
get state() {
|
|
2185
|
+
return {
|
|
2186
|
+
mode: this._mode,
|
|
2187
|
+
strictRevocation: this._strictRevocation,
|
|
2188
|
+
ready: this.ready,
|
|
2189
|
+
tokenVersion: this._tokenVersion,
|
|
2190
|
+
kid: this._signing?.kid
|
|
2191
|
+
};
|
|
2192
|
+
}
|
|
2193
|
+
/**
|
|
2194
|
+
* Registers a listener fired synchronously at the end of every applyConfig that changed
|
|
2195
|
+
* anything (including the first successful load). Returns an unsubscribe function.
|
|
2196
|
+
*/
|
|
2197
|
+
onChange(listener) {
|
|
2198
|
+
this._listeners.add(listener);
|
|
2199
|
+
return () => {
|
|
2200
|
+
this._listeners.delete(listener);
|
|
2201
|
+
};
|
|
2202
|
+
}
|
|
2203
|
+
fingerprint() {
|
|
2204
|
+
return JSON.stringify([this._loaded, this._signing, this.state]);
|
|
2205
|
+
}
|
|
2206
|
+
get mode() {
|
|
2207
|
+
return this._mode;
|
|
2208
|
+
}
|
|
2209
|
+
get strictRevocation() {
|
|
2210
|
+
return this._strictRevocation;
|
|
2211
|
+
}
|
|
2212
|
+
/** True once signing keys have been loaded. */
|
|
2213
|
+
get ready() {
|
|
2214
|
+
return !!this._signing?.publicKey;
|
|
2215
|
+
}
|
|
2216
|
+
get tokenVersion() {
|
|
2217
|
+
return this._tokenVersion;
|
|
2218
|
+
}
|
|
2219
|
+
/**
|
|
2220
|
+
* Loads the authentication config, subscribes to its updates and starts a slow periodic
|
|
2221
|
+
* refresh. The first attempt is awaited; if it fails, retries continue in the background
|
|
2222
|
+
* with capped backoff until the config is loaded and the subscription is acknowledged.
|
|
2223
|
+
* Idempotent while running; never throws.
|
|
2224
|
+
*/
|
|
2225
|
+
async start() {
|
|
2226
|
+
if (this._running) return;
|
|
2227
|
+
this._running = true;
|
|
2228
|
+
const generation = ++this._generation;
|
|
2229
|
+
await this.attempt(generation, INITIAL_RETRY_MS);
|
|
2230
|
+
if (generation !== this._generation) return;
|
|
2231
|
+
this._refreshTimer = setInterval(() => void this.refetch(true), REFRESH_INTERVAL_MS);
|
|
2232
|
+
this._refreshTimer.unref?.();
|
|
2233
|
+
}
|
|
2234
|
+
stop() {
|
|
2235
|
+
this._running = false;
|
|
2236
|
+
this._generation++;
|
|
2237
|
+
if (this._retryTimer) clearTimeout(this._retryTimer);
|
|
2238
|
+
if (this._refreshTimer) clearInterval(this._refreshTimer);
|
|
2239
|
+
this._retryTimer = void 0;
|
|
2240
|
+
this._refreshTimer = void 0;
|
|
2241
|
+
if (this._subscribed) {
|
|
2242
|
+
try {
|
|
2243
|
+
this.grpcSdk.bus?.unsubscribe(SUBSCRIBER_ID);
|
|
2244
|
+
} catch {
|
|
2245
|
+
}
|
|
2246
|
+
this._subscribed = false;
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
async attempt(generation, delay) {
|
|
2250
|
+
if (generation !== this._generation) return;
|
|
2251
|
+
try {
|
|
2252
|
+
await this.subscribe(generation);
|
|
2253
|
+
if (!this._loaded) {
|
|
2254
|
+
this.applyConfig(await this.grpcSdk.config.get("authentication"));
|
|
2255
|
+
}
|
|
2256
|
+
} catch {
|
|
2257
|
+
}
|
|
2258
|
+
if (generation !== this._generation) return;
|
|
2259
|
+
if (this._subscribed && this._loaded) return;
|
|
2260
|
+
this._retryTimer = setTimeout(
|
|
2261
|
+
() => void this.attempt(generation, Math.min(delay * 2, MAX_RETRY_MS)),
|
|
2262
|
+
delay
|
|
2263
|
+
);
|
|
2264
|
+
this._retryTimer.unref?.();
|
|
2265
|
+
}
|
|
2266
|
+
async subscribe(generation) {
|
|
2267
|
+
if (this._subscribed) return;
|
|
2268
|
+
const bus = this.grpcSdk.bus;
|
|
2269
|
+
if (!bus) return;
|
|
2270
|
+
await bus.subscribeAck(
|
|
2271
|
+
AUTHENTICATION_CONFIG_UPDATE_CHANNEL,
|
|
2272
|
+
(message) => {
|
|
2273
|
+
try {
|
|
2274
|
+
this.applyConfig(JSON.parse(message));
|
|
2275
|
+
} catch {
|
|
2276
|
+
}
|
|
2277
|
+
},
|
|
2278
|
+
SUBSCRIBER_ID
|
|
2279
|
+
);
|
|
2280
|
+
if (generation !== this._generation) {
|
|
2281
|
+
bus.unsubscribe(SUBSCRIBER_ID);
|
|
2282
|
+
return;
|
|
2283
|
+
}
|
|
2284
|
+
this._subscribed = true;
|
|
2285
|
+
}
|
|
2286
|
+
/** Refetches the config from the authentication module; failures keep the previous state. */
|
|
2287
|
+
async refetch(force = false) {
|
|
2288
|
+
const now = Date.now();
|
|
2289
|
+
if (this._refetching) return;
|
|
2290
|
+
if (!force && now - this._lastRefetch < UNKNOWN_KID_REFETCH_MIN_MS) return;
|
|
2291
|
+
this._lastRefetch = now;
|
|
2292
|
+
this._refetching = true;
|
|
2293
|
+
try {
|
|
2294
|
+
this.applyConfig(await this.grpcSdk.config.get("authentication"));
|
|
2295
|
+
} catch {
|
|
2296
|
+
} finally {
|
|
2297
|
+
this._refetching = false;
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
warnOnce(key, message) {
|
|
2301
|
+
if (this._warned.has(key)) return;
|
|
2302
|
+
this._warned.add(key);
|
|
2303
|
+
ConduitGrpcSdk10.Logger.warn(`AccessTokenVerifier: ${message}`);
|
|
2304
|
+
}
|
|
2305
|
+
static validPem(pem) {
|
|
2306
|
+
if (typeof pem !== "string" || !pem) return false;
|
|
2307
|
+
try {
|
|
2308
|
+
createPublicKey(pem);
|
|
2309
|
+
return true;
|
|
2310
|
+
} catch {
|
|
2311
|
+
return false;
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
/**
|
|
2315
|
+
* Validates and normalizes the signing keys. Returns undefined when no signing block is
|
|
2316
|
+
* configured, null when the public key is invalid (the caller keeps the previous state).
|
|
2317
|
+
*/
|
|
2318
|
+
normalizeSigning(s) {
|
|
2319
|
+
if (!(s && typeof s.kid === "string" && s.kid && s.publicKey)) return void 0;
|
|
2320
|
+
if (!_AccessTokenVerifier.validPem(s.publicKey)) {
|
|
2321
|
+
this.warnOnce("publicKey", "invalid signing public key, keeping previous state");
|
|
2322
|
+
return null;
|
|
2323
|
+
}
|
|
2324
|
+
const previousPublicKey = this.validPreviousKey(s.previousPublicKey);
|
|
2325
|
+
return {
|
|
2326
|
+
kid: s.kid,
|
|
2327
|
+
publicKey: s.publicKey,
|
|
2328
|
+
previousKid: previousPublicKey ? s.previousKid || void 0 : void 0,
|
|
2329
|
+
previousPublicKey,
|
|
2330
|
+
previousRetiredAt: Number(s.previousRetiredAt ?? 0)
|
|
2331
|
+
};
|
|
2332
|
+
}
|
|
2333
|
+
validPreviousKey(pem) {
|
|
2334
|
+
if (!pem) return void 0;
|
|
2335
|
+
if (_AccessTokenVerifier.validPem(pem)) return pem;
|
|
2336
|
+
this.warnOnce("previousPublicKey", "invalid previous public key, ignoring it");
|
|
2337
|
+
return void 0;
|
|
2338
|
+
}
|
|
2339
|
+
parseTokenVersion(raw) {
|
|
2340
|
+
const version = Number(raw ?? 0);
|
|
2341
|
+
if (Number.isNaN(version)) {
|
|
2342
|
+
this.warnOnce("tokenVersion", "invalid tokenVersion, all tokens will be rejected");
|
|
2343
|
+
}
|
|
2344
|
+
return version;
|
|
2345
|
+
}
|
|
2346
|
+
notifyListeners() {
|
|
2347
|
+
const state = this.state;
|
|
2348
|
+
for (const listener of [...this._listeners]) {
|
|
2349
|
+
try {
|
|
2350
|
+
listener(state);
|
|
2351
|
+
} catch (e) {
|
|
2352
|
+
ConduitGrpcSdk10.Logger.warn(
|
|
2353
|
+
`AccessTokenVerifier: change listener failed: ${e?.message}`
|
|
2354
|
+
);
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
/**
|
|
2359
|
+
* Applies an authentication module config object (full config). Invalid input is ignored
|
|
2360
|
+
* and the previous state is kept; invalid keys are logged once.
|
|
2361
|
+
*/
|
|
2362
|
+
applyConfig(config) {
|
|
2363
|
+
if (!isConfigWithAccessTokens(config)) return;
|
|
2364
|
+
const tokens = config.accessTokens;
|
|
2365
|
+
const before = this.fingerprint();
|
|
2366
|
+
const signing = this.normalizeSigning(tokens.signing);
|
|
2367
|
+
if (signing === null) return;
|
|
2368
|
+
const version = this.parseTokenVersion(tokens.tokenVersion);
|
|
2369
|
+
this._mode = tokens.mode === "jwt" ? "jwt" : "session";
|
|
2370
|
+
this._tokenVersion = version;
|
|
2371
|
+
this._strictRevocation = tokens.strictRevocation === true;
|
|
2372
|
+
this._signing = signing;
|
|
2373
|
+
this._loaded = true;
|
|
2374
|
+
if (this.fingerprint() !== before) this.notifyListeners();
|
|
2375
|
+
}
|
|
2376
|
+
/**
|
|
2377
|
+
* Verifies an access token, throwing GrpcError(UNAUTHENTICATED) with a short reason on failure.
|
|
2378
|
+
* `options.now` is epoch milliseconds. An `unknown kid` triggers a throttled (at most once
|
|
2379
|
+
* per 10 s) background refetch of the config, to pick up a rotation sooner.
|
|
2380
|
+
*/
|
|
2381
|
+
verify(token, options) {
|
|
2382
|
+
if (!this._signing) {
|
|
2383
|
+
throw new GrpcError5(status7.UNAUTHENTICATED, "verifier not ready");
|
|
2384
|
+
}
|
|
2385
|
+
try {
|
|
2386
|
+
return verifyAccessToken(
|
|
2387
|
+
token,
|
|
2388
|
+
{ tokenVersion: this._tokenVersion, signing: this._signing },
|
|
2389
|
+
options
|
|
2390
|
+
);
|
|
2391
|
+
} catch (e) {
|
|
2392
|
+
if (e.message === "unknown kid") void this.refetch();
|
|
2393
|
+
throw e;
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
};
|
|
2397
|
+
|
|
1866
2398
|
// src/routing/RoutingController.ts
|
|
1867
|
-
import { status as
|
|
2399
|
+
import { status as status8 } from "@grpc/grpc-js";
|
|
1868
2400
|
var RoutingController = class {
|
|
1869
2401
|
_routeHandlers = {};
|
|
1870
2402
|
handleRequest(call, callback) {
|
|
@@ -1872,7 +2404,7 @@ var RoutingController = class {
|
|
|
1872
2404
|
this._routeHandlers[call.request.functionName](call, callback);
|
|
1873
2405
|
} else {
|
|
1874
2406
|
callback({
|
|
1875
|
-
code:
|
|
2407
|
+
code: status8.NOT_FOUND,
|
|
1876
2408
|
message: "Not found"
|
|
1877
2409
|
});
|
|
1878
2410
|
}
|
|
@@ -1931,13 +2463,13 @@ var ManagedModule = class extends ConduitServiceModule {
|
|
|
1931
2463
|
await this.preRegisterLifecycle();
|
|
1932
2464
|
await this.grpcSdk.config.registerModule(this.name, this.address, this.healthState);
|
|
1933
2465
|
} catch (err) {
|
|
1934
|
-
|
|
1935
|
-
|
|
2466
|
+
ConduitGrpcSdk11.Logger.error("Failed to initialize server");
|
|
2467
|
+
ConduitGrpcSdk11.Logger.error(err);
|
|
1936
2468
|
process.exit(-1);
|
|
1937
2469
|
}
|
|
1938
2470
|
await this.postRegisterLifecycle().catch((err) => {
|
|
1939
|
-
|
|
1940
|
-
|
|
2471
|
+
ConduitGrpcSdk11.Logger.error("Failed to activate module");
|
|
2472
|
+
ConduitGrpcSdk11.Logger.error(err);
|
|
1941
2473
|
process.exit(-1);
|
|
1942
2474
|
});
|
|
1943
2475
|
}
|
|
@@ -2004,11 +2536,11 @@ var ManagedModule = class extends ConduitServiceModule {
|
|
|
2004
2536
|
* Registers common and module-specific metric types.
|
|
2005
2537
|
*/
|
|
2006
2538
|
async registerMetrics() {
|
|
2007
|
-
if (
|
|
2008
|
-
|
|
2539
|
+
if (ConduitGrpcSdk11.Metrics) {
|
|
2540
|
+
ConduitGrpcSdk11.Metrics.initializeDefaultMetrics();
|
|
2009
2541
|
if (this.metricsSchema) {
|
|
2010
2542
|
Object.values(this.metricsSchema).forEach((metric) => {
|
|
2011
|
-
|
|
2543
|
+
ConduitGrpcSdk11.Metrics.registerMetric(metric.type, metric.config);
|
|
2012
2544
|
});
|
|
2013
2545
|
}
|
|
2014
2546
|
}
|
|
@@ -2032,7 +2564,7 @@ var ManagedModule = class extends ConduitServiceModule {
|
|
|
2032
2564
|
const known = new Set(this.grpcSdk.getKnownModuleNames());
|
|
2033
2565
|
for (const n of names) {
|
|
2034
2566
|
if (!known.has(n)) {
|
|
2035
|
-
|
|
2567
|
+
ConduitGrpcSdk11.Logger.warn(
|
|
2036
2568
|
`Declared Conduit peer "${n}" is not a known built-in module (see ConduitGrpcSdk.getKnownModuleNames).`
|
|
2037
2569
|
);
|
|
2038
2570
|
}
|
|
@@ -2093,18 +2625,19 @@ var ManagedModule = class extends ConduitServiceModule {
|
|
|
2093
2625
|
this.service.functions
|
|
2094
2626
|
);
|
|
2095
2627
|
}
|
|
2628
|
+
RoutingManager.grpcSdk = this.grpcSdk;
|
|
2096
2629
|
RoutingManager.ClientController = new RoutingController();
|
|
2097
2630
|
RoutingManager.AdminController = new RoutingController();
|
|
2098
2631
|
await this.addHealthCheckService();
|
|
2099
2632
|
await this.addModuleService();
|
|
2100
2633
|
await this.grpcServer.start();
|
|
2101
|
-
|
|
2634
|
+
ConduitGrpcSdk11.Logger.log("gRPC server listening on " + this._port);
|
|
2102
2635
|
}
|
|
2103
2636
|
async setConfig(call, callback) {
|
|
2104
2637
|
try {
|
|
2105
2638
|
if (!this.config) {
|
|
2106
2639
|
return callback({
|
|
2107
|
-
code:
|
|
2640
|
+
code: status9.INVALID_ARGUMENT,
|
|
2108
2641
|
message: "Module is not configurable"
|
|
2109
2642
|
});
|
|
2110
2643
|
}
|
|
@@ -2123,7 +2656,7 @@ var ManagedModule = class extends ConduitServiceModule {
|
|
|
2123
2656
|
this.config = convict(this.configSchema);
|
|
2124
2657
|
this.config.load(previousConfig);
|
|
2125
2658
|
return callback({
|
|
2126
|
-
code:
|
|
2659
|
+
code: status9.INVALID_ARGUMENT,
|
|
2127
2660
|
message: "Invalid configuration values: " + e.message
|
|
2128
2661
|
});
|
|
2129
2662
|
}
|
|
@@ -2132,7 +2665,7 @@ var ManagedModule = class extends ConduitServiceModule {
|
|
|
2132
2665
|
await this._drainConfigQueue();
|
|
2133
2666
|
this.grpcSdk.bus?.publish(kebabCase(this.name) + ":config:update", configJson);
|
|
2134
2667
|
} catch (e) {
|
|
2135
|
-
return callback({ code:
|
|
2668
|
+
return callback({ code: status9.INTERNAL, message: e.message });
|
|
2136
2669
|
}
|
|
2137
2670
|
}
|
|
2138
2671
|
/** Used to update the module's configuration on initial Redis/DB reconciliation. */
|
|
@@ -2212,8 +2745,8 @@ var ManagedModule = class extends ConduitServiceModule {
|
|
|
2212
2745
|
};
|
|
2213
2746
|
|
|
2214
2747
|
// src/classes/ModuleError.ts
|
|
2215
|
-
import { GrpcError as
|
|
2216
|
-
var ModuleError = class extends
|
|
2748
|
+
import { GrpcError as GrpcError6 } from "@conduitplatform/grpc-sdk";
|
|
2749
|
+
var ModuleError = class extends GrpcError6 {
|
|
2217
2750
|
debugLogInfo;
|
|
2218
2751
|
constructor(errorDefinition, debugLogInfo) {
|
|
2219
2752
|
const { grpcCode, conduitCode, message } = errorDefinition;
|
|
@@ -2228,6 +2761,8 @@ var ModuleError = class extends GrpcError2 {
|
|
|
2228
2761
|
}
|
|
2229
2762
|
};
|
|
2230
2763
|
export {
|
|
2764
|
+
AUTHENTICATION_CONFIG_UPDATE_CHANNEL,
|
|
2765
|
+
AccessTokenVerifier,
|
|
2231
2766
|
ConduitActiveSchema,
|
|
2232
2767
|
ConduitBoolean,
|
|
2233
2768
|
ConduitDate,
|
|
@@ -2238,6 +2773,7 @@ export {
|
|
|
2238
2773
|
ConduitServiceModule,
|
|
2239
2774
|
ConduitString,
|
|
2240
2775
|
ConfigController,
|
|
2776
|
+
DEFAULT_LEEWAY_SECONDS,
|
|
2241
2777
|
DEFAULT_STRIP_FIELDS,
|
|
2242
2778
|
GrpcServer,
|
|
2243
2779
|
ManagedModule,
|
|
@@ -2250,20 +2786,27 @@ export {
|
|
|
2250
2786
|
convictConfigParser,
|
|
2251
2787
|
createParsedRouterRequest,
|
|
2252
2788
|
createServer,
|
|
2789
|
+
createUserHydrator,
|
|
2253
2790
|
exportModel,
|
|
2791
|
+
getAuthMiddlewareKind,
|
|
2792
|
+
hydrateContextUser,
|
|
2254
2793
|
importModel,
|
|
2255
2794
|
initializeSdk,
|
|
2256
2795
|
linearBackoffTimeout,
|
|
2257
2796
|
linearBackoffTimeoutAsync,
|
|
2797
|
+
mapHydrationErrorCode,
|
|
2258
2798
|
merge,
|
|
2259
2799
|
readConduitPeersManifest,
|
|
2260
2800
|
reconcileStoredModuleConfig,
|
|
2261
2801
|
redactSensitiveConfig,
|
|
2262
2802
|
restoreRedactedSecrets,
|
|
2263
2803
|
sanitizeDocumentsForExport,
|
|
2804
|
+
selectVerificationKey,
|
|
2805
|
+
shouldHydrateUser,
|
|
2264
2806
|
stableConfigJson,
|
|
2265
2807
|
storedConfigsEquivalent,
|
|
2266
2808
|
stripInternalFields,
|
|
2809
|
+
verifyAccessToken,
|
|
2267
2810
|
wrapFunctionsAsync,
|
|
2268
2811
|
wrapGrpcFunctions,
|
|
2269
2812
|
wrapRouterGrpcFunction
|