@stardeck-customer-apps/testing 0.5.1 → 0.6.1
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/SKILL.md +57 -6
- package/dist/index.d.mts +16 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +373 -30
- package/dist/index.mjs +373 -30
- package/dist/setup.js +293 -38
- package/dist/setup.mjs +293 -38
- package/package.json +1 -1
package/dist/setup.mjs
CHANGED
|
@@ -63,6 +63,14 @@ var TEST_ENV_DEFAULTS = {
|
|
|
63
63
|
// src/simulator/hmac.ts
|
|
64
64
|
import crypto from "crypto";
|
|
65
65
|
var TIMESTAMP_TOLERANCE_SECONDS = 300;
|
|
66
|
+
function isNonEmptyString(value) {
|
|
67
|
+
return typeof value === "string" && value.length > 0;
|
|
68
|
+
}
|
|
69
|
+
function isDeploymentAuthPayload(value) {
|
|
70
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
71
|
+
const payload = value;
|
|
72
|
+
return payload.type === "deployment-request" && isNonEmptyString(payload.organizationId) && isNonEmptyString(payload.projectId) && isNonEmptyString(payload.deploymentId) && typeof payload.timestamp === "number" && Number.isSafeInteger(payload.timestamp) && isNonEmptyString(payload.nonce);
|
|
73
|
+
}
|
|
66
74
|
function verifyDeploymentAuthHeader(secret, header) {
|
|
67
75
|
const dotIndex = header.lastIndexOf(".");
|
|
68
76
|
if (dotIndex === -1) return null;
|
|
@@ -80,16 +88,16 @@ function verifyDeploymentAuthHeader(secret, header) {
|
|
|
80
88
|
if (expectedBuf.length !== actualBuf.length || !crypto.timingSafeEqual(expectedBuf, actualBuf)) {
|
|
81
89
|
return null;
|
|
82
90
|
}
|
|
83
|
-
let
|
|
91
|
+
let parsed;
|
|
84
92
|
try {
|
|
85
|
-
|
|
93
|
+
parsed = JSON.parse(payloadJson);
|
|
86
94
|
} catch {
|
|
87
95
|
return null;
|
|
88
96
|
}
|
|
89
|
-
if (
|
|
97
|
+
if (!isDeploymentAuthPayload(parsed)) return null;
|
|
90
98
|
const now3 = Math.floor(Date.now() / 1e3);
|
|
91
|
-
if (Math.abs(now3 -
|
|
92
|
-
return
|
|
99
|
+
if (Math.abs(now3 - parsed.timestamp) > TIMESTAMP_TOLERANCE_SECONDS) return null;
|
|
100
|
+
return parsed;
|
|
93
101
|
}
|
|
94
102
|
|
|
95
103
|
// src/simulator/http.ts
|
|
@@ -577,25 +585,128 @@ async function handleEmailSend(request) {
|
|
|
577
585
|
|
|
578
586
|
// src/simulator/identities.ts
|
|
579
587
|
import crypto3 from "crypto";
|
|
580
|
-
var
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
"
|
|
588
|
-
|
|
588
|
+
var ATTACH_LINK_KINDS = /* @__PURE__ */ new Set(["line", "facebook", "instagram", "email", "phone"]);
|
|
589
|
+
var RESOLVE_KINDS = /* @__PURE__ */ new Set(["phone", "email", "line"]);
|
|
590
|
+
var E164_PHONE = /^\+[1-9]\d{1,14}$/;
|
|
591
|
+
var SEARCH_DEFAULT_LIMIT = 50;
|
|
592
|
+
var SEARCH_MAX_LIMIT = 100;
|
|
593
|
+
function asciiLowercaseTrim(value) {
|
|
594
|
+
const trimmed = value.trim();
|
|
595
|
+
let out = "";
|
|
596
|
+
for (let i = 0; i < trimmed.length; i++) {
|
|
597
|
+
const code = trimmed.charCodeAt(i);
|
|
598
|
+
out += code >= 65 && code <= 90 ? String.fromCharCode(code + 32) : trimmed[i];
|
|
599
|
+
}
|
|
600
|
+
return out;
|
|
601
|
+
}
|
|
602
|
+
var IdentityGraphError = class extends Error {
|
|
603
|
+
};
|
|
589
604
|
function now() {
|
|
590
605
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
591
606
|
}
|
|
592
607
|
function linksFor(identityId) {
|
|
593
608
|
return state.identityLinks.filter((l) => l.identityId === identityId);
|
|
594
609
|
}
|
|
610
|
+
function comparableExternalId(kind, externalId) {
|
|
611
|
+
if (kind === "email") return asciiLowercaseTrim(externalId);
|
|
612
|
+
return externalId;
|
|
613
|
+
}
|
|
614
|
+
function normalizeExternalId(kind, externalId) {
|
|
615
|
+
if (kind === "email") {
|
|
616
|
+
const normalized = asciiLowercaseTrim(externalId);
|
|
617
|
+
return normalized.length > 0 ? { externalId: normalized } : { error: "externalId is required" };
|
|
618
|
+
}
|
|
619
|
+
if (kind === "phone" && !E164_PHONE.test(externalId)) {
|
|
620
|
+
return { error: "phone externalId must be an E.164 number" };
|
|
621
|
+
}
|
|
622
|
+
return { externalId };
|
|
623
|
+
}
|
|
624
|
+
function normalizeLinkInput(kind, externalId, allowedKinds, kindError) {
|
|
625
|
+
if (typeof kind !== "string" || !allowedKinds.has(kind)) {
|
|
626
|
+
return { error: kindError };
|
|
627
|
+
}
|
|
628
|
+
if (typeof externalId !== "string" || externalId.length === 0) {
|
|
629
|
+
return { error: "externalId is required" };
|
|
630
|
+
}
|
|
631
|
+
const normalized = normalizeExternalId(kind, externalId);
|
|
632
|
+
if ("error" in normalized) return normalized;
|
|
633
|
+
return { kind, externalId: normalized.externalId };
|
|
634
|
+
}
|
|
635
|
+
function normalizeResolveInput(kind, externalId) {
|
|
636
|
+
const normalized = normalizeLinkInput(
|
|
637
|
+
kind,
|
|
638
|
+
externalId,
|
|
639
|
+
RESOLVE_KINDS,
|
|
640
|
+
"kind must be one of: phone, email, line"
|
|
641
|
+
);
|
|
642
|
+
if ("error" in normalized) return normalized;
|
|
643
|
+
return { kind: normalized.kind, externalId: normalized.externalId };
|
|
644
|
+
}
|
|
645
|
+
function canonicalIdentity(identityId) {
|
|
646
|
+
const visited = /* @__PURE__ */ new Set();
|
|
647
|
+
let identity = state.identities.get(identityId);
|
|
648
|
+
while (identity) {
|
|
649
|
+
if (visited.has(identity.id)) {
|
|
650
|
+
throw new IdentityGraphError("identity merge chain contains a cycle");
|
|
651
|
+
}
|
|
652
|
+
visited.add(identity.id);
|
|
653
|
+
if (identity.status === "archived") {
|
|
654
|
+
throw new IdentityGraphError("identity is archived");
|
|
655
|
+
}
|
|
656
|
+
if (identity.status === "active" && !identity.mergedIntoId) {
|
|
657
|
+
return identity;
|
|
658
|
+
}
|
|
659
|
+
if (!identity.mergedIntoId) {
|
|
660
|
+
throw new IdentityGraphError("identity merge chain has no active canonical target");
|
|
661
|
+
}
|
|
662
|
+
identity = state.identities.get(identity.mergedIntoId);
|
|
663
|
+
}
|
|
664
|
+
throw new IdentityGraphError("identity merge chain references a missing identity");
|
|
665
|
+
}
|
|
666
|
+
function matchingLinks(kind, externalId) {
|
|
667
|
+
const comparable = comparableExternalId(kind, externalId);
|
|
668
|
+
return state.identityLinks.filter(
|
|
669
|
+
(link) => link.kind === kind && comparableExternalId(link.kind, link.externalId) === comparable
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
function createPerson(displayName) {
|
|
673
|
+
const createdAt = now();
|
|
674
|
+
const identity = {
|
|
675
|
+
id: crypto3.randomUUID(),
|
|
676
|
+
type: "person",
|
|
677
|
+
parentId: null,
|
|
678
|
+
displayName,
|
|
679
|
+
profile: {},
|
|
680
|
+
status: "active",
|
|
681
|
+
mergedIntoId: null,
|
|
682
|
+
externalRef: null,
|
|
683
|
+
createdAt,
|
|
684
|
+
updatedAt: createdAt
|
|
685
|
+
};
|
|
686
|
+
state.identities.set(identity.id, identity);
|
|
687
|
+
return identity;
|
|
688
|
+
}
|
|
689
|
+
function createUnverifiedLink(identityId, kind, externalId) {
|
|
690
|
+
const link = {
|
|
691
|
+
id: crypto3.randomUUID(),
|
|
692
|
+
identityId,
|
|
693
|
+
kind,
|
|
694
|
+
externalId,
|
|
695
|
+
// Deployment credentials can attest provenance, but cannot manufacture a
|
|
696
|
+
// platform-verified fact. Trusted channel/control-plane setup uses the
|
|
697
|
+
// directory's test-only seedVerifiedLink helper instead.
|
|
698
|
+
verified: false,
|
|
699
|
+
createdAt: now()
|
|
700
|
+
};
|
|
701
|
+
state.identityLinks.push(link);
|
|
702
|
+
return link;
|
|
703
|
+
}
|
|
595
704
|
function handleList(request) {
|
|
596
705
|
const typeParam = new URL(request.url).searchParams.get("type");
|
|
597
706
|
const type = typeParam === "person" || typeParam === "account" ? typeParam : void 0;
|
|
598
|
-
const identities = [...state.identities.values()]
|
|
707
|
+
const identities = [...state.identities.values()].filter(
|
|
708
|
+
(identity) => identity.status !== "merged"
|
|
709
|
+
);
|
|
599
710
|
return success({ identities: type ? identities.filter((i) => i.type === type) : identities });
|
|
600
711
|
}
|
|
601
712
|
async function handleCreate(request) {
|
|
@@ -613,6 +724,7 @@ async function handleCreate(request) {
|
|
|
613
724
|
}
|
|
614
725
|
parentId = parent.id;
|
|
615
726
|
}
|
|
727
|
+
const createdAt = now();
|
|
616
728
|
const identity = {
|
|
617
729
|
id: crypto3.randomUUID(),
|
|
618
730
|
type,
|
|
@@ -622,8 +734,8 @@ async function handleCreate(request) {
|
|
|
622
734
|
status: "active",
|
|
623
735
|
mergedIntoId: null,
|
|
624
736
|
externalRef: body.externalRef ?? null,
|
|
625
|
-
createdAt
|
|
626
|
-
updatedAt:
|
|
737
|
+
createdAt,
|
|
738
|
+
updatedAt: createdAt
|
|
627
739
|
};
|
|
628
740
|
state.identities.set(identity.id, identity);
|
|
629
741
|
return success({ identity });
|
|
@@ -659,36 +771,166 @@ async function handleAttachLink(identityId, request) {
|
|
|
659
771
|
return failure("links attach only to active persons");
|
|
660
772
|
}
|
|
661
773
|
const body = await readJsonBody(request);
|
|
662
|
-
const
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
}
|
|
670
|
-
const existing =
|
|
774
|
+
const normalized = normalizeLinkInput(
|
|
775
|
+
body.kind,
|
|
776
|
+
body.externalId,
|
|
777
|
+
ATTACH_LINK_KINDS,
|
|
778
|
+
`kind must be one of: ${[...ATTACH_LINK_KINDS].join(", ")}`
|
|
779
|
+
);
|
|
780
|
+
if ("error" in normalized) return failure(normalized.error);
|
|
781
|
+
const { kind, externalId } = normalized;
|
|
782
|
+
const [existing] = matchingLinks(kind, externalId);
|
|
671
783
|
if (existing) {
|
|
672
784
|
if (existing.identityId === identityId) return success({ link: existing });
|
|
673
785
|
return failure("identifier already linked to another identity", 409);
|
|
674
786
|
}
|
|
675
|
-
const link =
|
|
676
|
-
id: crypto3.randomUUID(),
|
|
677
|
-
identityId,
|
|
678
|
-
kind,
|
|
679
|
-
externalId,
|
|
680
|
-
verified: body.verified === true,
|
|
681
|
-
createdAt: now()
|
|
682
|
-
};
|
|
683
|
-
state.identityLinks.push(link);
|
|
787
|
+
const link = createUnverifiedLink(identityId, kind, externalId);
|
|
684
788
|
return success({ link });
|
|
685
789
|
}
|
|
790
|
+
async function handleResolve(request) {
|
|
791
|
+
const body = await readJsonBody(request);
|
|
792
|
+
const normalized = normalizeResolveInput(body.kind, body.externalId);
|
|
793
|
+
if ("error" in normalized) return failure(normalized.error);
|
|
794
|
+
const provenance = body.provenance;
|
|
795
|
+
if (provenance !== "guest" && provenance !== "staff") {
|
|
796
|
+
return failure("provenance must be 'guest' or 'staff'");
|
|
797
|
+
}
|
|
798
|
+
if (body.displayName !== void 0 && typeof body.displayName !== "string") {
|
|
799
|
+
return failure("displayName must be a string");
|
|
800
|
+
}
|
|
801
|
+
const links = matchingLinks(normalized.kind, normalized.externalId);
|
|
802
|
+
if (links.length > 0) {
|
|
803
|
+
const canonicalIds = /* @__PURE__ */ new Set();
|
|
804
|
+
for (const link of links) {
|
|
805
|
+
try {
|
|
806
|
+
canonicalIds.add(canonicalIdentity(link.identityId).id);
|
|
807
|
+
} catch (error) {
|
|
808
|
+
const message = error instanceof Error ? error.message : "invalid identity merge chain";
|
|
809
|
+
return failure(`cannot resolve identity link: ${message}`, 409);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
if (canonicalIds.size !== 1) {
|
|
813
|
+
return failure("identifier is linked to multiple identities", 409);
|
|
814
|
+
}
|
|
815
|
+
const canonicalId = [...canonicalIds][0];
|
|
816
|
+
const hasVerifiedLink = links.some((link) => link.verified);
|
|
817
|
+
if (provenance === "guest" && hasVerifiedLink) {
|
|
818
|
+
return success({ identityId: null, reason: "verified_conflict" });
|
|
819
|
+
}
|
|
820
|
+
return success({ identityId: canonicalId, created: false });
|
|
821
|
+
}
|
|
822
|
+
const displayName = body.displayName ?? null;
|
|
823
|
+
const identity = createPerson(displayName);
|
|
824
|
+
createUnverifiedLink(identity.id, normalized.kind, normalized.externalId);
|
|
825
|
+
return success({ identityId: identity.id, created: true });
|
|
826
|
+
}
|
|
827
|
+
function encodeSearchCursor(cursor) {
|
|
828
|
+
return Buffer.from(JSON.stringify(cursor), "utf8").toString("base64url");
|
|
829
|
+
}
|
|
830
|
+
function decodeSearchCursor(raw, query) {
|
|
831
|
+
try {
|
|
832
|
+
const rawParsed = JSON.parse(Buffer.from(raw, "base64url").toString("utf8"));
|
|
833
|
+
if (!rawParsed || typeof rawParsed !== "object" || Array.isArray(rawParsed)) return null;
|
|
834
|
+
const parsed = rawParsed;
|
|
835
|
+
if (typeof parsed.createdAt !== "string" || typeof parsed.id !== "string" || typeof parsed.query !== "string" || parsed.query !== query) {
|
|
836
|
+
return null;
|
|
837
|
+
}
|
|
838
|
+
return { createdAt: parsed.createdAt, id: parsed.id, query: parsed.query };
|
|
839
|
+
} catch {
|
|
840
|
+
return null;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
function compareIdentityOrder(left, right) {
|
|
844
|
+
const byCreatedAt = left.createdAt.localeCompare(right.createdAt);
|
|
845
|
+
return byCreatedAt !== 0 ? byCreatedAt : left.id.localeCompare(right.id);
|
|
846
|
+
}
|
|
847
|
+
function identityMatchesQuery(identity, query) {
|
|
848
|
+
if (!query) return true;
|
|
849
|
+
if (identity.displayName?.toLowerCase().includes(query)) return true;
|
|
850
|
+
return state.identityLinks.some(
|
|
851
|
+
(link) => link.identityId === identity.id && comparableExternalId(link.kind, link.externalId).toLowerCase().includes(query)
|
|
852
|
+
);
|
|
853
|
+
}
|
|
854
|
+
function handleSearch(request) {
|
|
855
|
+
const url = new URL(request.url);
|
|
856
|
+
const query = (url.searchParams.get("query") ?? "").trim().toLowerCase();
|
|
857
|
+
const rawLimit = url.searchParams.get("limit");
|
|
858
|
+
let limit = SEARCH_DEFAULT_LIMIT;
|
|
859
|
+
if (rawLimit !== null) {
|
|
860
|
+
if (!/^\d+$/.test(rawLimit)) return failure("limit must be a positive integer");
|
|
861
|
+
limit = Number(rawLimit);
|
|
862
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > SEARCH_MAX_LIMIT) {
|
|
863
|
+
return failure(`limit must be between 1 and ${SEARCH_MAX_LIMIT}`);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
const rawCursor = url.searchParams.get("cursor");
|
|
867
|
+
const cursor = rawCursor ? decodeSearchCursor(rawCursor, query) : null;
|
|
868
|
+
if (rawCursor && !cursor) return failure("cursor is invalid");
|
|
869
|
+
const identities = [...state.identities.values()].filter((identity) => identity.status === "active" && identity.mergedIntoId === null).filter((identity) => identityMatchesQuery(identity, query)).sort(compareIdentityOrder);
|
|
870
|
+
const start = cursor ? identities.findIndex((identity) => compareIdentityOrder(identity, cursor) > 0) : 0;
|
|
871
|
+
const pageStart = start < 0 ? identities.length : start;
|
|
872
|
+
const items = identities.slice(pageStart, pageStart + limit);
|
|
873
|
+
const hasMore = pageStart + items.length < identities.length;
|
|
874
|
+
const nextCursor = hasMore ? encodeSearchCursor({
|
|
875
|
+
createdAt: items[items.length - 1].createdAt,
|
|
876
|
+
id: items[items.length - 1].id,
|
|
877
|
+
query
|
|
878
|
+
}) : null;
|
|
879
|
+
return success({ items, nextCursor });
|
|
880
|
+
}
|
|
881
|
+
function aliasesForCanonical(canonicalId) {
|
|
882
|
+
const aliases = [canonicalId];
|
|
883
|
+
for (const identity of state.identities.values()) {
|
|
884
|
+
if (identity.id === canonicalId || identity.status !== "merged") continue;
|
|
885
|
+
try {
|
|
886
|
+
if (canonicalIdentity(identity.id).id === canonicalId) aliases.push(identity.id);
|
|
887
|
+
} catch {
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
return aliases;
|
|
891
|
+
}
|
|
892
|
+
async function handleAliases(request) {
|
|
893
|
+
const body = await readJsonBody(request);
|
|
894
|
+
if (!Array.isArray(body.identityIds)) {
|
|
895
|
+
return failure("identityIds must be an array");
|
|
896
|
+
}
|
|
897
|
+
if (body.identityIds.length === 0) {
|
|
898
|
+
return failure("identityIds must not be empty");
|
|
899
|
+
}
|
|
900
|
+
if (body.identityIds.length > 200) {
|
|
901
|
+
return failure("A maximum of 200 identity ids may be requested");
|
|
902
|
+
}
|
|
903
|
+
if (body.identityIds.some((identityId) => typeof identityId !== "string" || identityId.length === 0)) {
|
|
904
|
+
return failure("identityIds must contain non-empty strings");
|
|
905
|
+
}
|
|
906
|
+
const aliases = {};
|
|
907
|
+
for (const identityId of body.identityIds) {
|
|
908
|
+
let canonical;
|
|
909
|
+
try {
|
|
910
|
+
canonical = canonicalIdentity(identityId);
|
|
911
|
+
} catch (error) {
|
|
912
|
+
const message = error instanceof Error ? error.message : "invalid identity merge chain";
|
|
913
|
+
return failure(`cannot expand identity aliases: ${message}`, 409);
|
|
914
|
+
}
|
|
915
|
+
aliases[canonical.id] ??= aliasesForCanonical(canonical.id);
|
|
916
|
+
}
|
|
917
|
+
return success({ aliases });
|
|
918
|
+
}
|
|
686
919
|
async function handleIdentitiesRequest(request, subPath) {
|
|
687
920
|
const method = request.method;
|
|
688
921
|
if (subPath === "" || subPath === "/") {
|
|
689
922
|
if (method === "GET") return handleList(request);
|
|
690
923
|
if (method === "POST") return handleCreate(request);
|
|
691
924
|
}
|
|
925
|
+
if (subPath === "/resolve" && method === "POST") {
|
|
926
|
+
return handleResolve(request);
|
|
927
|
+
}
|
|
928
|
+
if (subPath === "/aliases" && method === "POST") {
|
|
929
|
+
return handleAliases(request);
|
|
930
|
+
}
|
|
931
|
+
if (subPath === "/search" && method === "GET") {
|
|
932
|
+
return handleSearch(request);
|
|
933
|
+
}
|
|
692
934
|
const linksMatch = subPath.match(/^\/([^/]+)\/links$/);
|
|
693
935
|
if (linksMatch && method === "POST") {
|
|
694
936
|
return handleAttachLink(linksMatch[1], request);
|
|
@@ -1337,13 +1579,24 @@ function requiresDeploymentHmac(request, url) {
|
|
|
1337
1579
|
const edgeMatch = url.pathname.match(/^\/api\/deployments\/[^/]+\/edge(\/.*)?$/);
|
|
1338
1580
|
return !!(dataStoreMatch || isEmail || identitiesMatch || storeMatch || messagingMatch || edgeMatch);
|
|
1339
1581
|
}
|
|
1582
|
+
function deploymentIdFromPath(url) {
|
|
1583
|
+
const match = url.pathname.match(/\/api\/deployments\/([^/]+)(?:\/|$)/);
|
|
1584
|
+
if (!match) return null;
|
|
1585
|
+
try {
|
|
1586
|
+
return decodeURIComponent(match[1]);
|
|
1587
|
+
} catch {
|
|
1588
|
+
return match[1];
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1340
1591
|
async function handleSimulatedRequest(request, url) {
|
|
1341
1592
|
if (url.pathname === "/sql") {
|
|
1342
1593
|
return handleNeonSql(requireDb(), request);
|
|
1343
1594
|
}
|
|
1344
|
-
const authMatch = url.pathname.match(
|
|
1595
|
+
const authMatch = url.pathname.match(
|
|
1596
|
+
/^\/api\/deployments\/[^/]+\/auth\/(v2\/verify|verify|refresh)$/
|
|
1597
|
+
);
|
|
1345
1598
|
if (authMatch) {
|
|
1346
|
-
return authMatch[1]
|
|
1599
|
+
return authMatch[1].endsWith("verify") ? handleAuthVerify(request) : handleAuthRefresh(request);
|
|
1347
1600
|
}
|
|
1348
1601
|
const dataStoreMatch = url.pathname.match(/^\/api\/data-stores\/[^/]+(\/.*)?$/);
|
|
1349
1602
|
const isEmail = url.pathname === "/api/email/send";
|
|
@@ -1360,7 +1613,9 @@ async function handleSimulatedRequest(request, url) {
|
|
|
1360
1613
|
return failure("Missing authentication header", 401);
|
|
1361
1614
|
}
|
|
1362
1615
|
const secret = process.env.DEPLOYMENT_SECRET;
|
|
1363
|
-
|
|
1616
|
+
const authPayload = secret ? verifyDeploymentAuthHeader(secret, authHeader) : null;
|
|
1617
|
+
const pathDeploymentId = deploymentIdFromPath(url);
|
|
1618
|
+
if (!authPayload || pathDeploymentId !== null && authPayload.deploymentId !== pathDeploymentId) {
|
|
1364
1619
|
return failure("Invalid authentication", 401);
|
|
1365
1620
|
}
|
|
1366
1621
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stardeck-customer-apps/testing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Vitest test harness for Stardeck customer apps — in-process Postgres (PGlite) plus a control-plane simulator so the real Stardeck SDKs run unmodified in tests",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|