@eldrin-project/eldrin-app-core 0.0.1 → 0.0.3
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/dist/index.cjs +621 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +824 -2
- package/dist/index.d.ts +824 -2
- package/dist/index.js +603 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -559,18 +559,638 @@ function createApp(options) {
|
|
|
559
559
|
};
|
|
560
560
|
}
|
|
561
561
|
|
|
562
|
+
// src/events/client.ts
|
|
563
|
+
var EldrinEventClient = class {
|
|
564
|
+
appId;
|
|
565
|
+
coreApiUrl;
|
|
566
|
+
constructor(config) {
|
|
567
|
+
this.appId = config.appId;
|
|
568
|
+
this.coreApiUrl = config.coreApiUrl.replace(/\/$/, "");
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Emit an event to the event bus
|
|
572
|
+
*
|
|
573
|
+
* @param type - Event type (must be declared in app's manifest emits)
|
|
574
|
+
* @param payload - Event payload data
|
|
575
|
+
* @param options - Optional emit configuration
|
|
576
|
+
* @returns Result containing the event ID
|
|
577
|
+
* @throws Error if app is not authorized to emit this event type
|
|
578
|
+
*/
|
|
579
|
+
async emit(type, payload, options = {}) {
|
|
580
|
+
const response = await fetch(`${this.coreApiUrl}/api/events/emit`, {
|
|
581
|
+
method: "POST",
|
|
582
|
+
headers: {
|
|
583
|
+
"Content-Type": "application/json",
|
|
584
|
+
"X-Eldrin-App-Id": this.appId
|
|
585
|
+
},
|
|
586
|
+
body: JSON.stringify({
|
|
587
|
+
type,
|
|
588
|
+
payload,
|
|
589
|
+
version: options.version ?? 1,
|
|
590
|
+
idempotencyKey: options.idempotencyKey
|
|
591
|
+
})
|
|
592
|
+
});
|
|
593
|
+
if (!response.ok) {
|
|
594
|
+
const errorData = await response.json().catch(() => ({ error: "Unknown error" }));
|
|
595
|
+
throw new Error(errorData.error || `Failed to emit event: ${response.status}`);
|
|
596
|
+
}
|
|
597
|
+
return response.json();
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Poll for pending events
|
|
601
|
+
*
|
|
602
|
+
* @param options - Optional polling configuration
|
|
603
|
+
* @returns List of pending event deliveries
|
|
604
|
+
*/
|
|
605
|
+
async poll(options = {}) {
|
|
606
|
+
const params = new URLSearchParams();
|
|
607
|
+
if (options.limit) params.set("limit", String(options.limit));
|
|
608
|
+
if (options.types?.length) params.set("types", options.types.join(","));
|
|
609
|
+
const url = `${this.coreApiUrl}/api/events/poll${params.toString() ? `?${params}` : ""}`;
|
|
610
|
+
const response = await fetch(url, {
|
|
611
|
+
headers: {
|
|
612
|
+
"X-Eldrin-App-Id": this.appId
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
if (!response.ok) {
|
|
616
|
+
throw new Error(`Failed to poll events: ${response.status}`);
|
|
617
|
+
}
|
|
618
|
+
return response.json();
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Acknowledge successful event processing
|
|
622
|
+
*
|
|
623
|
+
* @param deliveryId - The delivery record ID to acknowledge
|
|
624
|
+
*/
|
|
625
|
+
async ack(deliveryId) {
|
|
626
|
+
const response = await fetch(`${this.coreApiUrl}/api/events/ack`, {
|
|
627
|
+
method: "POST",
|
|
628
|
+
headers: {
|
|
629
|
+
"Content-Type": "application/json",
|
|
630
|
+
"X-Eldrin-App-Id": this.appId
|
|
631
|
+
},
|
|
632
|
+
body: JSON.stringify({ deliveryId })
|
|
633
|
+
});
|
|
634
|
+
if (!response.ok) {
|
|
635
|
+
throw new Error(`Failed to acknowledge event: ${response.status}`);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Negative acknowledge - event will be retried later
|
|
640
|
+
*
|
|
641
|
+
* @param deliveryId - The delivery record ID
|
|
642
|
+
* @param error - Optional error message for logging
|
|
643
|
+
*/
|
|
644
|
+
async nack(deliveryId, error) {
|
|
645
|
+
const response = await fetch(`${this.coreApiUrl}/api/events/nack`, {
|
|
646
|
+
method: "POST",
|
|
647
|
+
headers: {
|
|
648
|
+
"Content-Type": "application/json",
|
|
649
|
+
"X-Eldrin-App-Id": this.appId
|
|
650
|
+
},
|
|
651
|
+
body: JSON.stringify({ deliveryId, error })
|
|
652
|
+
});
|
|
653
|
+
if (!response.ok) {
|
|
654
|
+
throw new Error(`Failed to nack event: ${response.status}`);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Get all registered event types
|
|
659
|
+
*
|
|
660
|
+
* @returns List of all registered event types across all apps
|
|
661
|
+
*/
|
|
662
|
+
async getEventTypes() {
|
|
663
|
+
const response = await fetch(`${this.coreApiUrl}/api/events/types`, {
|
|
664
|
+
headers: {
|
|
665
|
+
"X-Eldrin-App-Id": this.appId
|
|
666
|
+
}
|
|
667
|
+
});
|
|
668
|
+
if (!response.ok) {
|
|
669
|
+
throw new Error(`Failed to get event types: ${response.status}`);
|
|
670
|
+
}
|
|
671
|
+
const data = await response.json();
|
|
672
|
+
return data.types;
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Get subscriptions for this app
|
|
676
|
+
*
|
|
677
|
+
* @returns List of active subscriptions
|
|
678
|
+
*/
|
|
679
|
+
async getSubscriptions() {
|
|
680
|
+
const response = await fetch(`${this.coreApiUrl}/api/events/subscriptions`, {
|
|
681
|
+
headers: {
|
|
682
|
+
"X-Eldrin-App-Id": this.appId
|
|
683
|
+
}
|
|
684
|
+
});
|
|
685
|
+
if (!response.ok) {
|
|
686
|
+
throw new Error(`Failed to get subscriptions: ${response.status}`);
|
|
687
|
+
}
|
|
688
|
+
const data = await response.json();
|
|
689
|
+
return data.subscriptions;
|
|
690
|
+
}
|
|
691
|
+
};
|
|
692
|
+
function createEventClient(env, appId) {
|
|
693
|
+
const coreApiUrl = env.ELDRIN_CORE_URL || "http://localhost:4000";
|
|
694
|
+
return new EldrinEventClient({ appId, coreApiUrl });
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// src/auth/index.ts
|
|
698
|
+
var AUTH_HEADERS = {
|
|
699
|
+
USER_ID: "x-eldrin-user-id",
|
|
700
|
+
USER_EMAIL: "x-eldrin-user-email",
|
|
701
|
+
USER_NAME: "x-eldrin-user-name",
|
|
702
|
+
USER_ROLES: "x-eldrin-user-roles",
|
|
703
|
+
USER_PERMISSIONS: "x-eldrin-user-permissions"
|
|
704
|
+
};
|
|
705
|
+
function base64UrlDecode(str) {
|
|
706
|
+
const base64 = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
707
|
+
const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
|
|
708
|
+
const binary = atob(padded);
|
|
709
|
+
const bytes = new Uint8Array(binary.length);
|
|
710
|
+
for (let i = 0; i < binary.length; i++) {
|
|
711
|
+
bytes[i] = binary.charCodeAt(i);
|
|
712
|
+
}
|
|
713
|
+
return bytes;
|
|
714
|
+
}
|
|
715
|
+
async function verifyJWTSignature(token, secret) {
|
|
716
|
+
const parts = token.split(".");
|
|
717
|
+
if (parts.length !== 3) {
|
|
718
|
+
return { valid: false, error: "Invalid JWT format" };
|
|
719
|
+
}
|
|
720
|
+
const [headerB64, payloadB64, signatureB64] = parts;
|
|
721
|
+
try {
|
|
722
|
+
const encoder = new TextEncoder();
|
|
723
|
+
const keyData = encoder.encode(secret);
|
|
724
|
+
const key = await crypto.subtle.importKey(
|
|
725
|
+
"raw",
|
|
726
|
+
keyData,
|
|
727
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
728
|
+
false,
|
|
729
|
+
["verify"]
|
|
730
|
+
);
|
|
731
|
+
const signedData = encoder.encode(`${headerB64}.${payloadB64}`);
|
|
732
|
+
const signature = base64UrlDecode(signatureB64);
|
|
733
|
+
const isValid = await crypto.subtle.verify(
|
|
734
|
+
"HMAC",
|
|
735
|
+
key,
|
|
736
|
+
signature.buffer,
|
|
737
|
+
signedData
|
|
738
|
+
);
|
|
739
|
+
if (!isValid) {
|
|
740
|
+
return { valid: false, error: "Invalid signature" };
|
|
741
|
+
}
|
|
742
|
+
const payloadJson = new TextDecoder().decode(base64UrlDecode(payloadB64));
|
|
743
|
+
const payload = JSON.parse(payloadJson);
|
|
744
|
+
const now = Date.now();
|
|
745
|
+
if (payload.exp && payload.exp < now) {
|
|
746
|
+
return { valid: false, error: "Token expired" };
|
|
747
|
+
}
|
|
748
|
+
return { valid: true, payload };
|
|
749
|
+
} catch (error) {
|
|
750
|
+
return {
|
|
751
|
+
valid: false,
|
|
752
|
+
error: error instanceof Error ? error.message : "JWT verification failed"
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
function extractAppPermissions(payload, appId, developerId) {
|
|
757
|
+
const permissions = [];
|
|
758
|
+
for (const [key, perms] of Object.entries(payload.appPermissions)) {
|
|
759
|
+
if (developerId) {
|
|
760
|
+
if (key === `${developerId}:${appId}`) {
|
|
761
|
+
permissions.push(...perms);
|
|
762
|
+
}
|
|
763
|
+
} else {
|
|
764
|
+
if (key.endsWith(`:${appId}`)) {
|
|
765
|
+
permissions.push(...perms);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
return permissions;
|
|
770
|
+
}
|
|
771
|
+
function jwtPayloadToAuthContext(payload, appId, developerId) {
|
|
772
|
+
return {
|
|
773
|
+
userId: payload.sub,
|
|
774
|
+
email: payload.email,
|
|
775
|
+
name: `${payload.firstName} ${payload.lastName}`.trim(),
|
|
776
|
+
platformRoles: payload.platformRoles,
|
|
777
|
+
permissions: extractAppPermissions(payload, appId, developerId)
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
async function verifyJWT(request, options) {
|
|
781
|
+
const authHeader = request.headers.get("Authorization");
|
|
782
|
+
if (!authHeader) {
|
|
783
|
+
return { success: false, error: "Missing Authorization header" };
|
|
784
|
+
}
|
|
785
|
+
if (!authHeader.startsWith("Bearer ")) {
|
|
786
|
+
return { success: false, error: "Invalid Authorization header format" };
|
|
787
|
+
}
|
|
788
|
+
const token = authHeader.slice(7);
|
|
789
|
+
const result = await verifyJWTSignature(token, options.secret);
|
|
790
|
+
if (!result.valid || !result.payload) {
|
|
791
|
+
return { success: false, error: result.error || "JWT verification failed" };
|
|
792
|
+
}
|
|
793
|
+
const auth = jwtPayloadToAuthContext(result.payload, options.appId, options.developerId);
|
|
794
|
+
return { success: true, auth };
|
|
795
|
+
}
|
|
796
|
+
async function getAuthContextFromJWT(request, options) {
|
|
797
|
+
const authHeader = request.headers.get("Authorization");
|
|
798
|
+
if (authHeader?.startsWith("Bearer ")) {
|
|
799
|
+
const result = await verifyJWT(request, options);
|
|
800
|
+
if (result.success) {
|
|
801
|
+
return result.auth;
|
|
802
|
+
}
|
|
803
|
+
return null;
|
|
804
|
+
}
|
|
805
|
+
return getAuthContext(request);
|
|
806
|
+
}
|
|
807
|
+
async function requireJWTAuth(request, options) {
|
|
808
|
+
const auth = await getAuthContextFromJWT(request, options);
|
|
809
|
+
if (!auth) {
|
|
810
|
+
return new Response(JSON.stringify({ error: "Unauthorized" }), {
|
|
811
|
+
status: 401,
|
|
812
|
+
headers: { "Content-Type": "application/json" }
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
return auth;
|
|
816
|
+
}
|
|
817
|
+
async function requireJWTPermission(request, options, resource, action) {
|
|
818
|
+
const auth = await requireJWTAuth(request, options);
|
|
819
|
+
if (auth instanceof Response) {
|
|
820
|
+
return auth;
|
|
821
|
+
}
|
|
822
|
+
if (isPlatformAdmin(auth)) {
|
|
823
|
+
return auth;
|
|
824
|
+
}
|
|
825
|
+
if (!hasPermission(auth, resource, action)) {
|
|
826
|
+
return new Response(
|
|
827
|
+
JSON.stringify({
|
|
828
|
+
error: "Forbidden",
|
|
829
|
+
message: `Missing permission: ${resource}:${action}`
|
|
830
|
+
}),
|
|
831
|
+
{
|
|
832
|
+
status: 403,
|
|
833
|
+
headers: { "Content-Type": "application/json" }
|
|
834
|
+
}
|
|
835
|
+
);
|
|
836
|
+
}
|
|
837
|
+
return auth;
|
|
838
|
+
}
|
|
839
|
+
function getAuthContext(request) {
|
|
840
|
+
const userId = request.headers.get(AUTH_HEADERS.USER_ID);
|
|
841
|
+
if (!userId) {
|
|
842
|
+
return null;
|
|
843
|
+
}
|
|
844
|
+
const email = request.headers.get(AUTH_HEADERS.USER_EMAIL) || "";
|
|
845
|
+
const name = request.headers.get(AUTH_HEADERS.USER_NAME) || "";
|
|
846
|
+
const rolesHeader = request.headers.get(AUTH_HEADERS.USER_ROLES) || "";
|
|
847
|
+
const permissionsHeader = request.headers.get(AUTH_HEADERS.USER_PERMISSIONS) || "";
|
|
848
|
+
return {
|
|
849
|
+
userId,
|
|
850
|
+
email,
|
|
851
|
+
name,
|
|
852
|
+
platformRoles: rolesHeader ? rolesHeader.split(",").map((r) => r.trim()) : [],
|
|
853
|
+
permissions: permissionsHeader ? permissionsHeader.split(",").map((p) => p.trim()) : []
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
function requireAuth(request) {
|
|
857
|
+
const auth = getAuthContext(request);
|
|
858
|
+
if (!auth) {
|
|
859
|
+
return new Response(JSON.stringify({ error: "Unauthorized" }), {
|
|
860
|
+
status: 401,
|
|
861
|
+
headers: { "Content-Type": "application/json" }
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
return auth;
|
|
865
|
+
}
|
|
866
|
+
function hasPermission(auth, resource, action) {
|
|
867
|
+
const required = `${resource}:${action}`;
|
|
868
|
+
if (auth.permissions.includes(required)) {
|
|
869
|
+
return true;
|
|
870
|
+
}
|
|
871
|
+
if (auth.permissions.includes(`${resource}:*`)) {
|
|
872
|
+
return true;
|
|
873
|
+
}
|
|
874
|
+
if (auth.permissions.includes("*:*")) {
|
|
875
|
+
return true;
|
|
876
|
+
}
|
|
877
|
+
return false;
|
|
878
|
+
}
|
|
879
|
+
function hasPlatformRole(auth, role) {
|
|
880
|
+
return auth.platformRoles.includes(role);
|
|
881
|
+
}
|
|
882
|
+
function isPlatformAdmin(auth) {
|
|
883
|
+
return auth.platformRoles.includes("admin");
|
|
884
|
+
}
|
|
885
|
+
function requirePermission(request, resource, action) {
|
|
886
|
+
const auth = requireAuth(request);
|
|
887
|
+
if (auth instanceof Response) {
|
|
888
|
+
return auth;
|
|
889
|
+
}
|
|
890
|
+
if (isPlatformAdmin(auth)) {
|
|
891
|
+
return auth;
|
|
892
|
+
}
|
|
893
|
+
if (!hasPermission(auth, resource, action)) {
|
|
894
|
+
return new Response(
|
|
895
|
+
JSON.stringify({
|
|
896
|
+
error: "Forbidden",
|
|
897
|
+
message: `Missing permission: ${resource}:${action}`
|
|
898
|
+
}),
|
|
899
|
+
{
|
|
900
|
+
status: 403,
|
|
901
|
+
headers: { "Content-Type": "application/json" }
|
|
902
|
+
}
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
return auth;
|
|
906
|
+
}
|
|
907
|
+
function requireAnyPermission(request, permissions) {
|
|
908
|
+
const auth = requireAuth(request);
|
|
909
|
+
if (auth instanceof Response) {
|
|
910
|
+
return auth;
|
|
911
|
+
}
|
|
912
|
+
if (isPlatformAdmin(auth)) {
|
|
913
|
+
return auth;
|
|
914
|
+
}
|
|
915
|
+
const hasAny = permissions.some(
|
|
916
|
+
([resource, action]) => hasPermission(auth, resource, action)
|
|
917
|
+
);
|
|
918
|
+
if (!hasAny) {
|
|
919
|
+
const permList = permissions.map(([r, a]) => `${r}:${a}`).join(", ");
|
|
920
|
+
return new Response(
|
|
921
|
+
JSON.stringify({
|
|
922
|
+
error: "Forbidden",
|
|
923
|
+
message: `Missing one of permissions: ${permList}`
|
|
924
|
+
}),
|
|
925
|
+
{
|
|
926
|
+
status: 403,
|
|
927
|
+
headers: { "Content-Type": "application/json" }
|
|
928
|
+
}
|
|
929
|
+
);
|
|
930
|
+
}
|
|
931
|
+
return auth;
|
|
932
|
+
}
|
|
933
|
+
function requireAllPermissions(request, permissions) {
|
|
934
|
+
const auth = requireAuth(request);
|
|
935
|
+
if (auth instanceof Response) {
|
|
936
|
+
return auth;
|
|
937
|
+
}
|
|
938
|
+
if (isPlatformAdmin(auth)) {
|
|
939
|
+
return auth;
|
|
940
|
+
}
|
|
941
|
+
const missing = permissions.filter(
|
|
942
|
+
([resource, action]) => !hasPermission(auth, resource, action)
|
|
943
|
+
);
|
|
944
|
+
if (missing.length > 0) {
|
|
945
|
+
const permList = missing.map(([r, a]) => `${r}:${a}`).join(", ");
|
|
946
|
+
return new Response(
|
|
947
|
+
JSON.stringify({
|
|
948
|
+
error: "Forbidden",
|
|
949
|
+
message: `Missing permissions: ${permList}`
|
|
950
|
+
}),
|
|
951
|
+
{
|
|
952
|
+
status: 403,
|
|
953
|
+
headers: { "Content-Type": "application/json" }
|
|
954
|
+
}
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
return auth;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// src/middleware/matcher.ts
|
|
961
|
+
function compileRoutes(routes, apiPrefix) {
|
|
962
|
+
return routes.map((route) => {
|
|
963
|
+
const methods = new Set(
|
|
964
|
+
Array.isArray(route.method) ? route.method.map((m) => m.toUpperCase()) : [route.method.toUpperCase()]
|
|
965
|
+
);
|
|
966
|
+
const fullPath = `${apiPrefix}${route.path}`;
|
|
967
|
+
const paramNames = [];
|
|
968
|
+
let regexPattern = fullPath.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, (_, name) => {
|
|
969
|
+
paramNames.push(name);
|
|
970
|
+
return "([^/]+)";
|
|
971
|
+
}).replace(/\*/g, "[^/]+");
|
|
972
|
+
const pattern = new RegExp(`^${regexPattern}$`);
|
|
973
|
+
let permission = null;
|
|
974
|
+
if (route.permission) {
|
|
975
|
+
const colonIndex = route.permission.indexOf(":");
|
|
976
|
+
if (colonIndex > 0) {
|
|
977
|
+
permission = {
|
|
978
|
+
resource: route.permission.substring(0, colonIndex),
|
|
979
|
+
action: route.permission.substring(colonIndex + 1)
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
return {
|
|
984
|
+
methods,
|
|
985
|
+
pattern,
|
|
986
|
+
paramNames,
|
|
987
|
+
permission,
|
|
988
|
+
originalPath: route.path
|
|
989
|
+
};
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
function matchRoute(method, pathname, compiledRoutes) {
|
|
993
|
+
const upperMethod = method.toUpperCase();
|
|
994
|
+
for (const route of compiledRoutes) {
|
|
995
|
+
if (!route.methods.has(upperMethod)) continue;
|
|
996
|
+
const match = pathname.match(route.pattern);
|
|
997
|
+
if (match) {
|
|
998
|
+
const params = {};
|
|
999
|
+
route.paramNames.forEach((name, index) => {
|
|
1000
|
+
params[name] = match[index + 1];
|
|
1001
|
+
});
|
|
1002
|
+
return { route, params };
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
return null;
|
|
1006
|
+
}
|
|
1007
|
+
function isPublicRoute(pathname, publicRoutes, apiPrefix) {
|
|
1008
|
+
for (const publicPath of publicRoutes) {
|
|
1009
|
+
const fullPath = `${apiPrefix}${publicPath}`;
|
|
1010
|
+
if (pathname === fullPath) return true;
|
|
1011
|
+
if (publicPath.endsWith("/*")) {
|
|
1012
|
+
const prefix = `${apiPrefix}${publicPath.slice(0, -2)}`;
|
|
1013
|
+
if (pathname.startsWith(prefix + "/")) return true;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
return false;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// src/middleware/index.ts
|
|
1020
|
+
function createPermissionMiddleware(config) {
|
|
1021
|
+
const apiPrefix = config.apiPrefix ?? "/api";
|
|
1022
|
+
const manifest = config.manifest;
|
|
1023
|
+
const api = manifest.api;
|
|
1024
|
+
const compiledRoutes = api?.routes ? compileRoutes(api.routes, apiPrefix) : [];
|
|
1025
|
+
const defaultPolicy = api?.defaultPolicy ?? "deny";
|
|
1026
|
+
const publicRoutes = api?.publicRoutes ?? [];
|
|
1027
|
+
const emptyAuth = {
|
|
1028
|
+
userId: "",
|
|
1029
|
+
email: "",
|
|
1030
|
+
name: "",
|
|
1031
|
+
platformRoles: [],
|
|
1032
|
+
permissions: []
|
|
1033
|
+
};
|
|
1034
|
+
function createCorsHeaders() {
|
|
1035
|
+
if (!config.cors) return {};
|
|
1036
|
+
return {
|
|
1037
|
+
"Access-Control-Allow-Origin": config.cors.allowOrigin,
|
|
1038
|
+
"Access-Control-Allow-Methods": config.cors.allowMethods,
|
|
1039
|
+
"Access-Control-Allow-Headers": config.cors.allowHeaders
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
function withCors(response) {
|
|
1043
|
+
if (!config.cors) return response;
|
|
1044
|
+
const newHeaders = new Headers(response.headers);
|
|
1045
|
+
Object.entries(createCorsHeaders()).forEach(([key, value]) => {
|
|
1046
|
+
newHeaders.set(key, value);
|
|
1047
|
+
});
|
|
1048
|
+
return new Response(response.body, {
|
|
1049
|
+
status: response.status,
|
|
1050
|
+
statusText: response.statusText,
|
|
1051
|
+
headers: newHeaders
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
function errorResponse(error) {
|
|
1055
|
+
if (config.onError) {
|
|
1056
|
+
return withCors(config.onError(error));
|
|
1057
|
+
}
|
|
1058
|
+
switch (error.type) {
|
|
1059
|
+
case "unauthorized":
|
|
1060
|
+
return withCors(
|
|
1061
|
+
Response.json(
|
|
1062
|
+
{ error: "Unauthorized", message: error.message },
|
|
1063
|
+
{ status: 401 }
|
|
1064
|
+
)
|
|
1065
|
+
);
|
|
1066
|
+
case "forbidden":
|
|
1067
|
+
return withCors(
|
|
1068
|
+
Response.json(
|
|
1069
|
+
{
|
|
1070
|
+
error: "Forbidden",
|
|
1071
|
+
message: error.message,
|
|
1072
|
+
permission: error.permission
|
|
1073
|
+
},
|
|
1074
|
+
{ status: 403 }
|
|
1075
|
+
)
|
|
1076
|
+
);
|
|
1077
|
+
case "route_not_found":
|
|
1078
|
+
return withCors(
|
|
1079
|
+
Response.json(
|
|
1080
|
+
{ error: "Not Found", message: error.message },
|
|
1081
|
+
{ status: 404 }
|
|
1082
|
+
)
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
async function verifyAndGetAuth(request, env) {
|
|
1087
|
+
const jwtOptions = {
|
|
1088
|
+
secret: config.getSecret(env),
|
|
1089
|
+
appId: manifest.id,
|
|
1090
|
+
developerId: manifest.developer_id
|
|
1091
|
+
};
|
|
1092
|
+
const result = await verifyJWT(request, jwtOptions);
|
|
1093
|
+
if (!result.success) {
|
|
1094
|
+
return errorResponse({
|
|
1095
|
+
type: "unauthorized",
|
|
1096
|
+
message: result.error
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
return result.auth;
|
|
1100
|
+
}
|
|
1101
|
+
return {
|
|
1102
|
+
async handle(request, env) {
|
|
1103
|
+
const url = new URL(request.url);
|
|
1104
|
+
const method = request.method;
|
|
1105
|
+
const pathname = url.pathname;
|
|
1106
|
+
if (config.skipRoutes) {
|
|
1107
|
+
for (const skipPattern of config.skipRoutes) {
|
|
1108
|
+
if (pathname.startsWith(skipPattern)) {
|
|
1109
|
+
return { auth: emptyAuth, url, params: {} };
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
if (method === "OPTIONS") {
|
|
1114
|
+
return {
|
|
1115
|
+
response: new Response(null, {
|
|
1116
|
+
status: 204,
|
|
1117
|
+
headers: createCorsHeaders()
|
|
1118
|
+
})
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
if (!pathname.startsWith(apiPrefix)) {
|
|
1122
|
+
return { auth: emptyAuth, url, params: {} };
|
|
1123
|
+
}
|
|
1124
|
+
if (isPublicRoute(pathname, publicRoutes, apiPrefix)) {
|
|
1125
|
+
return { auth: emptyAuth, url, params: {} };
|
|
1126
|
+
}
|
|
1127
|
+
const match = matchRoute(method, pathname, compiledRoutes);
|
|
1128
|
+
if (!match) {
|
|
1129
|
+
if (defaultPolicy === "allow") {
|
|
1130
|
+
return { auth: emptyAuth, url, params: {} };
|
|
1131
|
+
}
|
|
1132
|
+
const authResult2 = await verifyAndGetAuth(request, env);
|
|
1133
|
+
if (authResult2 instanceof Response) {
|
|
1134
|
+
return { response: authResult2 };
|
|
1135
|
+
}
|
|
1136
|
+
return { auth: authResult2, url, params: {} };
|
|
1137
|
+
}
|
|
1138
|
+
if (match.route.permission === null) {
|
|
1139
|
+
return { auth: emptyAuth, url, params: match.params };
|
|
1140
|
+
}
|
|
1141
|
+
const authResult = await verifyAndGetAuth(request, env);
|
|
1142
|
+
if (authResult instanceof Response) {
|
|
1143
|
+
return { response: authResult };
|
|
1144
|
+
}
|
|
1145
|
+
const auth = authResult;
|
|
1146
|
+
if (isPlatformAdmin(auth)) {
|
|
1147
|
+
return { auth, url, params: match.params };
|
|
1148
|
+
}
|
|
1149
|
+
const { resource, action } = match.route.permission;
|
|
1150
|
+
if (!hasPermission(auth, resource, action)) {
|
|
1151
|
+
return {
|
|
1152
|
+
response: errorResponse({
|
|
1153
|
+
type: "forbidden",
|
|
1154
|
+
message: `Missing permission: ${resource}:${action}`,
|
|
1155
|
+
permission: `${resource}:${action}`
|
|
1156
|
+
})
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
return { auth, url, params: match.params };
|
|
1160
|
+
}
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
exports.AUTH_HEADERS = AUTH_HEADERS;
|
|
562
1165
|
exports.CHECKSUM_PREFIX = CHECKSUM_PREFIX;
|
|
563
1166
|
exports.DatabaseProvider = DatabaseProvider;
|
|
1167
|
+
exports.EldrinEventClient = EldrinEventClient;
|
|
564
1168
|
exports.calculateChecksum = calculateChecksum;
|
|
565
1169
|
exports.calculatePrefixedChecksum = calculatePrefixedChecksum;
|
|
1170
|
+
exports.compileRoutes = compileRoutes;
|
|
566
1171
|
exports.createApp = createApp;
|
|
1172
|
+
exports.createEventClient = createEventClient;
|
|
1173
|
+
exports.createPermissionMiddleware = createPermissionMiddleware;
|
|
567
1174
|
exports.extractTimestamp = extractTimestamp;
|
|
568
1175
|
exports.generateMigrationManifest = generateMigrationManifest;
|
|
1176
|
+
exports.getAuthContext = getAuthContext;
|
|
1177
|
+
exports.getAuthContextFromJWT = getAuthContextFromJWT;
|
|
569
1178
|
exports.getMigrationStatus = getMigrationStatus;
|
|
570
1179
|
exports.getRollbackFilename = getRollbackFilename;
|
|
1180
|
+
exports.hasPermission = hasPermission;
|
|
1181
|
+
exports.hasPlatformRole = hasPlatformRole;
|
|
1182
|
+
exports.isPlatformAdmin = isPlatformAdmin;
|
|
1183
|
+
exports.isPublicRoute = isPublicRoute;
|
|
571
1184
|
exports.isValidMigrationFilename = isValidMigrationFilename;
|
|
572
1185
|
exports.isValidRollbackFilename = isValidRollbackFilename;
|
|
1186
|
+
exports.matchRoute = matchRoute;
|
|
573
1187
|
exports.parseSQLStatements = parseSQLStatements;
|
|
1188
|
+
exports.requireAllPermissions = requireAllPermissions;
|
|
1189
|
+
exports.requireAnyPermission = requireAnyPermission;
|
|
1190
|
+
exports.requireAuth = requireAuth;
|
|
1191
|
+
exports.requireJWTAuth = requireJWTAuth;
|
|
1192
|
+
exports.requireJWTPermission = requireJWTPermission;
|
|
1193
|
+
exports.requirePermission = requirePermission;
|
|
574
1194
|
exports.rollbackMigrations = rollbackMigrations;
|
|
575
1195
|
exports.runMigrations = runMigrations;
|
|
576
1196
|
exports.useDatabase = useDatabase;
|
|
@@ -578,5 +1198,6 @@ exports.useDatabaseContext = useDatabaseContext;
|
|
|
578
1198
|
exports.useMigrationsComplete = useMigrationsComplete;
|
|
579
1199
|
exports.validateMigrationManifest = validateMigrationManifest;
|
|
580
1200
|
exports.verifyChecksum = verifyChecksum;
|
|
1201
|
+
exports.verifyJWT = verifyJWT;
|
|
581
1202
|
//# sourceMappingURL=index.cjs.map
|
|
582
1203
|
//# sourceMappingURL=index.cjs.map
|