@valbuild/server 0.110.1 → 0.112.0

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.
@@ -301,8 +301,8 @@ export declare abstract class ValOps {
301
301
  * instances in the browser and would otherwise get no previews at all. See
302
302
  * #470.
303
303
  *
304
- * A string's `render` needs none of this — it is static config that travels
305
- * with the serialized schema.
304
+ * A `render`, and a string's `multiline`, need none of this — they are static
305
+ * config that travels with the serialized schema.
306
306
  */
307
307
  getPreviews(schemas: Schemas, sources: Sources): Promise<{
308
308
  previews: Record<ModuleFilePath, ReifiedPreview | null>;
@@ -5,4 +5,4 @@ import { ValidationError } from "@valbuild/core";
5
5
  * There is no schema in hand here — a `ValidationError` carries only the value
6
6
  * it flagged — so the shape is all there is to go on.
7
7
  */
8
- export declare function getValidationErrorFileRef(validationError: ValidationError): any;
8
+ export declare function getValidationErrorFileRef(validationError: ValidationError): string | null;
@@ -1,9 +1,54 @@
1
- export type JwtPayload = {
2
- sub: string;
3
- exp: number;
4
- org: string;
5
- project: string;
1
+ import { z } from "zod";
2
+ declare const JwtPayloadSchema: z.ZodObject<{
3
+ sub: z.ZodString;
4
+ exp: z.ZodNumber;
5
+ org: z.ZodString;
6
+ project: z.ZodString;
7
+ }, z.core.$loose>;
8
+ export type JwtPayload = z.infer<typeof JwtPayloadSchema>;
9
+ export type JwtFailureReason =
10
+ /** Not a JWT we issued: wrong number of segments, bad header, or a payload that is not a Val payload. */
11
+ "malformed"
12
+ /** The HMAC did not match: the token was forged or tampered with. */
13
+ | "invalid-signature"
14
+ /** The token parsed and verified, but `exp` is in the past. */
15
+ | "expired"
16
+ /** Programming error: {@link verifyJwt} was called without a secret. */
17
+ | "missing-secret";
18
+ export type JwtFailure = {
19
+ success: false;
20
+ reason: JwtFailureReason;
21
+ message: string;
6
22
  };
7
- export declare function decodeJwt(token: string, secretKey?: string): JwtPayload | null;
23
+ export type JwtResult = {
24
+ success: true;
25
+ data: JwtPayload;
26
+ } | JwtFailure;
27
+ /**
28
+ * Verify a JWT that we issued with {@link encodeJwt}, and return its payload.
29
+ *
30
+ * Checks, in order: the segment count, the header (`alg` is pinned to HS256, so
31
+ * algorithm confusion is rejected before the signature is looked at), the HMAC
32
+ * (compared in constant time), the payload shape, and `exp`.
33
+ *
34
+ * There is deliberately no way to make this skip the signature check: the
35
+ * secret is required, and an empty one is a failure rather than a token that
36
+ * validates against `HMAC("")`. Use {@link decodeJwtWithoutVerifying} - and read
37
+ * what it says - when you genuinely do not hold the signing key.
38
+ */
39
+ export declare function verifyJwt(token: string, secretKey: string): JwtResult;
40
+ /**
41
+ * Parse a JWT's payload **without checking its signature**.
42
+ *
43
+ * The header, the payload shape and `exp` are still validated, but nothing here
44
+ * establishes that the token was issued by anyone in particular. Only use this
45
+ * for a token whose authenticity is already established by the channel it
46
+ * arrived on - the app token we fetch from val.build over an api-key
47
+ * authenticated HTTPS request is the one such case. Anything that arrives from
48
+ * a browser (a cookie, a header, a query parameter) must go through
49
+ * {@link verifyJwt}.
50
+ */
51
+ export declare function decodeJwtWithoutVerifying(token: string): JwtResult;
8
52
  export declare function getExpire(): number;
9
53
  export declare function encodeJwt(payload: object, sessionKey: string): string;
54
+ export {};
@@ -1,4 +1,4 @@
1
- import { PatchId } from "@valbuild/core";
1
+ import { ModuleFilePath, PatchId } from "@valbuild/core";
2
2
  import { z } from "zod";
3
3
  import type { AuthorId, BaseSha } from "./ValOps.js";
4
4
  import { PatchLogEntry, PatchLogProblem } from "./patchLog.js";
@@ -32,14 +32,14 @@ export declare const FSPatch: z.ZodObject<{
32
32
  patch: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
33
33
  op: z.ZodLiteral<"add">;
34
34
  path: z.ZodArray<z.ZodString>;
35
- value: z.ZodType<JSONValueT, unknown, z.core.$ZodTypeInternals<JSONValueT, unknown>>;
35
+ value: z.ZodType<import("@valbuild/core/patch").JSONValue, unknown, z.core.$ZodTypeInternals<import("@valbuild/core/patch").JSONValue, unknown>>;
36
36
  }, z.core.$strict>, z.ZodObject<{
37
37
  op: z.ZodLiteral<"remove">;
38
38
  path: z.ZodTuple<[z.ZodString], z.ZodString>;
39
39
  }, z.core.$strict>, z.ZodObject<{
40
40
  op: z.ZodLiteral<"replace">;
41
41
  path: z.ZodArray<z.ZodString>;
42
- value: z.ZodType<JSONValueT, unknown, z.core.$ZodTypeInternals<JSONValueT, unknown>>;
42
+ value: z.ZodType<import("@valbuild/core/patch").JSONValue, unknown, z.core.$ZodTypeInternals<import("@valbuild/core/patch").JSONValue, unknown>>;
43
43
  }, z.core.$strict>, z.ZodObject<{
44
44
  op: z.ZodLiteral<"move">;
45
45
  from: z.ZodTuple<[z.ZodString], z.ZodString>;
@@ -51,15 +51,15 @@ export declare const FSPatch: z.ZodObject<{
51
51
  }, z.core.$strict>, z.ZodObject<{
52
52
  op: z.ZodLiteral<"test">;
53
53
  path: z.ZodArray<z.ZodString>;
54
- value: z.ZodType<JSONValueT, unknown, z.core.$ZodTypeInternals<JSONValueT, unknown>>;
54
+ value: z.ZodType<import("@valbuild/core/patch").JSONValue, unknown, z.core.$ZodTypeInternals<import("@valbuild/core/patch").JSONValue, unknown>>;
55
55
  }, z.core.$strict>, z.ZodObject<{
56
56
  op: z.ZodLiteral<"file">;
57
57
  path: z.ZodArray<z.ZodString>;
58
58
  filePath: z.ZodString;
59
- value: z.ZodType<JSONValueT, unknown, z.core.$ZodTypeInternals<JSONValueT, unknown>>;
59
+ value: z.ZodType<import("@valbuild/core/patch").JSONValue, unknown, z.core.$ZodTypeInternals<import("@valbuild/core/patch").JSONValue, unknown>>;
60
60
  remote: z.ZodBoolean;
61
61
  nestedFilePath: z.ZodOptional<z.ZodArray<z.ZodString>>;
62
- metadata: z.ZodOptional<z.ZodType<JSONValueT, unknown, z.core.$ZodTypeInternals<JSONValueT, unknown>>>;
62
+ metadata: z.ZodOptional<z.ZodType<import("@valbuild/core/patch").JSONValue, unknown, z.core.$ZodTypeInternals<import("@valbuild/core/patch").JSONValue, unknown>>>;
63
63
  }, z.core.$strict>], "op">>;
64
64
  patchId: z.ZodString;
65
65
  baseSha: z.ZodString & z.ZodType<BaseSha, string, z.core.$ZodTypeInternals<BaseSha, string>>;
@@ -1936,45 +1936,142 @@ class Service {
1936
1936
  }
1937
1937
  }
1938
1938
 
1939
- function decodeJwt(token, secretKey) {
1939
+ const JwtPayloadSchema = z.z.object({
1940
+ sub: z.z.string(),
1941
+ exp: z.z.number(),
1942
+ org: z.z.string(),
1943
+ project: z.z.string()
1944
+ })
1945
+ // NOTE: passthrough, because the session cookie payload carries more than
1946
+ // this (the val.build token, notably). This is the minimum every Val JWT has.
1947
+ .passthrough();
1948
+ /**
1949
+ * `exp` is an absolute Unix timestamp: a token is rejected once that moment has
1950
+ * passed, with this much slack to absorb clock drift between whoever signed the
1951
+ * token and us.
1952
+ */
1953
+ const CLOCK_SKEW_LEEWAY_SECONDS = 60;
1954
+ function failure(reason, message) {
1955
+ // NOTE: deliberately never logs the token itself - it is a credential, and
1956
+ // anything we print here ends up in the host's request logs.
1957
+ console.debug(`Invalid JWT (${reason}): ${message}`);
1958
+ return {
1959
+ success: false,
1960
+ reason,
1961
+ message
1962
+ };
1963
+ }
1964
+ function splitToken(token) {
1940
1965
  const [headerBase64, payloadBase64, signatureBase64, ...rest] = token.split(".");
1941
1966
  if (!headerBase64 || !payloadBase64 || !signatureBase64 || rest.length > 0) {
1942
- console.debug("Invalid JWT: format is not exactly {header}.{payload}.{signature}", token);
1943
1967
  return null;
1944
1968
  }
1969
+ return {
1970
+ headerBase64,
1971
+ payloadBase64,
1972
+ signatureBase64
1973
+ };
1974
+ }
1975
+
1976
+ /** Returns the failure if the header is not one of ours, or null if it is fine. */
1977
+ function verifyHeader(headerBase64) {
1978
+ let parsedHeader;
1945
1979
  try {
1946
- const parsedHeader = JSON.parse(Buffer.from(headerBase64, "base64").toString("utf8"));
1947
- const headerVerification = JwtHeaderSchema.safeParse(parsedHeader);
1948
- if (!headerVerification.success) {
1949
- console.debug("Invalid JWT: invalid header", parsedHeader);
1950
- return null;
1951
- }
1952
- if (headerVerification.data.typ !== jwtHeader.typ) {
1953
- console.debug("Invalid JWT: invalid header typ", parsedHeader);
1954
- return null;
1955
- }
1956
- if (headerVerification.data.alg !== jwtHeader.alg) {
1957
- console.debug("Invalid JWT: invalid header alg", parsedHeader);
1958
- return null;
1959
- }
1960
- } catch (err) {
1961
- console.debug("Invalid JWT: could not parse header", err);
1962
- return null;
1980
+ parsedHeader = JSON.parse(Buffer.from(headerBase64, "base64").toString("utf8"));
1981
+ } catch {
1982
+ return failure("malformed", "could not parse header");
1963
1983
  }
1964
- if (secretKey) {
1965
- const signature = crypto__default["default"].createHmac("sha256", secretKey).update(`${headerBase64}.${payloadBase64}`).digest("base64");
1966
- if (signature !== signatureBase64) {
1967
- console.debug("Invalid JWT: invalid signature");
1968
- return null;
1969
- }
1984
+ const headerVerification = JwtHeaderSchema.safeParse(parsedHeader);
1985
+ if (!headerVerification.success) {
1986
+ // NOTE: JwtHeaderSchema pins alg to HS256, so `alg: "none"` and every other
1987
+ // algorithm-confusion header is rejected here, before we look at the
1988
+ // signature at all.
1989
+ return failure("malformed", "unsupported header");
1970
1990
  }
1991
+ return null;
1992
+ }
1993
+ function parsePayload(payloadBase64) {
1994
+ let parsedPayload;
1971
1995
  try {
1972
- const parsedPayload = JSON.parse(Buffer.from(payloadBase64, "base64").toString("utf8"));
1973
- return parsedPayload;
1974
- } catch (err) {
1975
- console.debug("Invalid JWT: could not parse payload", err);
1976
- return null;
1996
+ parsedPayload = JSON.parse(Buffer.from(payloadBase64, "base64").toString("utf8"));
1997
+ } catch {
1998
+ return failure("malformed", "could not parse payload");
1977
1999
  }
2000
+ const payloadVerification = JwtPayloadSchema.safeParse(parsedPayload);
2001
+ if (!payloadVerification.success) {
2002
+ return failure("malformed", "payload is not a Val JWT payload");
2003
+ }
2004
+ const payload = payloadVerification.data;
2005
+ const nowSeconds = Math.floor(Date.now() / 1000);
2006
+ if (payload.exp + CLOCK_SKEW_LEEWAY_SECONDS <= nowSeconds) {
2007
+ return failure("expired", "token expired");
2008
+ }
2009
+ return {
2010
+ success: true,
2011
+ data: payload
2012
+ };
2013
+ }
2014
+
2015
+ /**
2016
+ * Verify a JWT that we issued with {@link encodeJwt}, and return its payload.
2017
+ *
2018
+ * Checks, in order: the segment count, the header (`alg` is pinned to HS256, so
2019
+ * algorithm confusion is rejected before the signature is looked at), the HMAC
2020
+ * (compared in constant time), the payload shape, and `exp`.
2021
+ *
2022
+ * There is deliberately no way to make this skip the signature check: the
2023
+ * secret is required, and an empty one is a failure rather than a token that
2024
+ * validates against `HMAC("")`. Use {@link decodeJwtWithoutVerifying} - and read
2025
+ * what it says - when you genuinely do not hold the signing key.
2026
+ */
2027
+ function verifyJwt(token, secretKey) {
2028
+ if (!secretKey) {
2029
+ return failure("missing-secret", "no secret key was provided");
2030
+ }
2031
+ const segments = splitToken(token);
2032
+ if (!segments) {
2033
+ return failure("malformed", "format is not exactly {header}.{payload}.{signature}");
2034
+ }
2035
+ const {
2036
+ headerBase64,
2037
+ payloadBase64,
2038
+ signatureBase64
2039
+ } = segments;
2040
+ const headerFailure = verifyHeader(headerBase64);
2041
+ if (headerFailure) {
2042
+ return headerFailure;
2043
+ }
2044
+ const expectedSignature = crypto__default["default"].createHmac("sha256", secretKey).update(`${headerBase64}.${payloadBase64}`).digest();
2045
+ const actualSignature = Buffer.from(signatureBase64, "base64");
2046
+ // NOTE: the length check is what makes timingSafeEqual usable at all (it
2047
+ // throws on a length mismatch), and the length of an HMAC-SHA256 is public.
2048
+ if (actualSignature.length !== expectedSignature.length || !crypto__default["default"].timingSafeEqual(actualSignature, expectedSignature)) {
2049
+ return failure("invalid-signature", "signature does not match");
2050
+ }
2051
+ return parsePayload(payloadBase64);
2052
+ }
2053
+
2054
+ /**
2055
+ * Parse a JWT's payload **without checking its signature**.
2056
+ *
2057
+ * The header, the payload shape and `exp` are still validated, but nothing here
2058
+ * establishes that the token was issued by anyone in particular. Only use this
2059
+ * for a token whose authenticity is already established by the channel it
2060
+ * arrived on - the app token we fetch from val.build over an api-key
2061
+ * authenticated HTTPS request is the one such case. Anything that arrives from
2062
+ * a browser (a cookie, a header, a query parameter) must go through
2063
+ * {@link verifyJwt}.
2064
+ */
2065
+ function decodeJwtWithoutVerifying(token) {
2066
+ const segments = splitToken(token);
2067
+ if (!segments) {
2068
+ return failure("malformed", "format is not exactly {header}.{payload}.{signature}");
2069
+ }
2070
+ const headerFailure = verifyHeader(segments.headerBase64);
2071
+ if (headerFailure) {
2072
+ return headerFailure;
2073
+ }
2074
+ return parsePayload(segments.payloadBase64);
1978
2075
  }
1979
2076
  function getExpire() {
1980
2077
  return Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 4; // 4 days
@@ -2610,8 +2707,8 @@ class ValOps {
2610
2707
  * instances in the browser and would otherwise get no previews at all. See
2611
2708
  * #470.
2612
2709
  *
2613
- * A string's `render` needs none of this — it is static config that travels
2614
- * with the serialized schema.
2710
+ * A `render`, and a string's `multiline`, need none of this — they are static
2711
+ * config that travels with the serialized schema.
2615
2712
  */
2616
2713
  async getPreviews(schemas, sources) {
2617
2714
  const previews = {};
@@ -7618,7 +7715,20 @@ const ValServer = (valModules, options, callbacks) => {
7618
7715
  }).then(async res => {
7619
7716
  if (res.status === 200) {
7620
7717
  const token = await res.text();
7621
- const verification = ValAppJwtPayload.safeParse(decodeJwt(token));
7718
+ // NOTE: this token is signed by val.build with a key we do not
7719
+ // hold, so we cannot verify it. What stands in for a signature check
7720
+ // is where it came from: the POST above, authenticated with the
7721
+ // project's api key, to the configured `valBuildUrl`. It is not user
7722
+ // input - but it is only as trustworthy as that host and that
7723
+ // channel, and `valBuildUrl` is overridable (`opts.valBuildUrl`,
7724
+ // `VAL_BUILD_URL`) and not required to be https. Point it at a plain
7725
+ // http host and this payload - which is re-signed into the session
7726
+ // cookie below - is whatever the network says it is.
7727
+ const decoded = decodeJwtWithoutVerifying(token);
7728
+ if (!decoded.success) {
7729
+ return null;
7730
+ }
7731
+ const verification = ValAppJwtPayload.safeParse(decoded.data);
7622
7732
  if (!verification.success) {
7623
7733
  return null;
7624
7734
  }
@@ -7650,8 +7760,8 @@ const ValServer = (valModules, options, callbacks) => {
7650
7760
  }
7651
7761
  }
7652
7762
  if (typeof cookie === "string") {
7653
- const decodedToken = decodeJwt(cookie, options.valSecret);
7654
- if (!decodedToken) {
7763
+ const verifiedToken = verifyJwt(cookie, options.valSecret);
7764
+ if (!verifiedToken.success) {
7655
7765
  if (serverOps instanceof ValOpsFS) {
7656
7766
  return {
7657
7767
  error: null,
@@ -7659,10 +7769,10 @@ const ValServer = (valModules, options, callbacks) => {
7659
7769
  };
7660
7770
  }
7661
7771
  return {
7662
- error: "Could not verify session (invalid token). You will need to login again."
7772
+ error: sessionErrorMessage(verifiedToken.reason)
7663
7773
  };
7664
7774
  }
7665
- const verification = IntegratedServerJwtPayload.safeParse(decodedToken);
7775
+ const verification = IntegratedServerJwtPayload.safeParse(verifiedToken.data);
7666
7776
  if (!verification.success) {
7667
7777
  if (serverOps instanceof ValOpsFS) {
7668
7778
  return {
@@ -7671,7 +7781,7 @@ const ValServer = (valModules, options, callbacks) => {
7671
7781
  };
7672
7782
  }
7673
7783
  return {
7674
- error: "Session invalid or, most likely, expired. You will need to login again."
7784
+ error: "Session invalid. You will need to login again."
7675
7785
  };
7676
7786
  }
7677
7787
  return {
@@ -10151,25 +10261,41 @@ const IntegratedServerJwtPayload = z.z.object({
10151
10261
  org: z.z.string(),
10152
10262
  project: z.z.string()
10153
10263
  });
10264
+ /**
10265
+ * The message we show the user when their session cookie does not verify. The
10266
+ * distinction matters: an expired session is normal and self-explanatory, while
10267
+ * a bad signature means the cookie was tampered with or `VAL_SECRET` changed.
10268
+ */
10269
+ function sessionErrorMessage(reason) {
10270
+ switch (reason) {
10271
+ case "expired":
10272
+ return "Session expired. You will need to login again.";
10273
+ case "malformed":
10274
+ case "invalid-signature":
10275
+ return "Could not verify session (invalid token). You will need to login again.";
10276
+ case "missing-secret":
10277
+ return "Setup is not correct: secret is missing";
10278
+ }
10279
+ }
10154
10280
  async function withAuth(secret, cookies, errorMessageType, handler) {
10155
10281
  const cookie = cookies[internal.VAL_SESSION_COOKIE];
10156
10282
  if (typeof cookie === "string") {
10157
- const decodedToken = decodeJwt(cookie, secret);
10158
- if (!decodedToken) {
10283
+ const verifiedToken = verifyJwt(cookie, secret);
10284
+ if (!verifiedToken.success) {
10159
10285
  return {
10160
10286
  status: 401,
10161
10287
  json: {
10162
- message: "Could not verify session. You will need to login again.",
10163
- details: "Invalid token"
10288
+ message: sessionErrorMessage(verifiedToken.reason),
10289
+ details: verifiedToken.reason
10164
10290
  }
10165
10291
  };
10166
10292
  }
10167
- const verification = IntegratedServerJwtPayload.safeParse(decodedToken);
10293
+ const verification = IntegratedServerJwtPayload.safeParse(verifiedToken.data);
10168
10294
  if (!verification.success) {
10169
10295
  return {
10170
10296
  status: 401,
10171
10297
  json: {
10172
- message: "Session invalid or, most likely, expired. You will need to login again.",
10298
+ message: "Session invalid. You will need to login again.",
10173
10299
  details: zodValidationError.fromError(verification.error).toString()
10174
10300
  }
10175
10301
  };
@@ -12862,7 +12988,7 @@ exports.createValApiRouter = createValApiRouter;
12862
12988
  exports.createValModuleFileInspector = createValModuleFileInspector;
12863
12989
  exports.createValServer = createValServer;
12864
12990
  exports.currentFixHandlers = currentFixHandlers;
12865
- exports.decodeJwt = decodeJwt;
12991
+ exports.decodeJwtWithoutVerifying = decodeJwtWithoutVerifying;
12866
12992
  exports.describePatchStoreProblems = describePatchStoreProblems;
12867
12993
  exports.downloadFileFromRemote = downloadFileFromRemote;
12868
12994
  exports.encodeJwt = encodeJwt;
@@ -12904,3 +13030,4 @@ exports.safeReadGit = safeReadGit;
12904
13030
  exports.startValLogin = startValLogin;
12905
13031
  exports.uploadRemoteFile = uploadRemoteFile;
12906
13032
  exports.validateMetadata = validateMetadata;
13033
+ exports.verifyJwt = verifyJwt;
@@ -1936,45 +1936,142 @@ class Service {
1936
1936
  }
1937
1937
  }
1938
1938
 
1939
- function decodeJwt(token, secretKey) {
1939
+ const JwtPayloadSchema = z.z.object({
1940
+ sub: z.z.string(),
1941
+ exp: z.z.number(),
1942
+ org: z.z.string(),
1943
+ project: z.z.string()
1944
+ })
1945
+ // NOTE: passthrough, because the session cookie payload carries more than
1946
+ // this (the val.build token, notably). This is the minimum every Val JWT has.
1947
+ .passthrough();
1948
+ /**
1949
+ * `exp` is an absolute Unix timestamp: a token is rejected once that moment has
1950
+ * passed, with this much slack to absorb clock drift between whoever signed the
1951
+ * token and us.
1952
+ */
1953
+ const CLOCK_SKEW_LEEWAY_SECONDS = 60;
1954
+ function failure(reason, message) {
1955
+ // NOTE: deliberately never logs the token itself - it is a credential, and
1956
+ // anything we print here ends up in the host's request logs.
1957
+ console.debug(`Invalid JWT (${reason}): ${message}`);
1958
+ return {
1959
+ success: false,
1960
+ reason,
1961
+ message
1962
+ };
1963
+ }
1964
+ function splitToken(token) {
1940
1965
  const [headerBase64, payloadBase64, signatureBase64, ...rest] = token.split(".");
1941
1966
  if (!headerBase64 || !payloadBase64 || !signatureBase64 || rest.length > 0) {
1942
- console.debug("Invalid JWT: format is not exactly {header}.{payload}.{signature}", token);
1943
1967
  return null;
1944
1968
  }
1969
+ return {
1970
+ headerBase64,
1971
+ payloadBase64,
1972
+ signatureBase64
1973
+ };
1974
+ }
1975
+
1976
+ /** Returns the failure if the header is not one of ours, or null if it is fine. */
1977
+ function verifyHeader(headerBase64) {
1978
+ let parsedHeader;
1945
1979
  try {
1946
- const parsedHeader = JSON.parse(Buffer.from(headerBase64, "base64").toString("utf8"));
1947
- const headerVerification = JwtHeaderSchema.safeParse(parsedHeader);
1948
- if (!headerVerification.success) {
1949
- console.debug("Invalid JWT: invalid header", parsedHeader);
1950
- return null;
1951
- }
1952
- if (headerVerification.data.typ !== jwtHeader.typ) {
1953
- console.debug("Invalid JWT: invalid header typ", parsedHeader);
1954
- return null;
1955
- }
1956
- if (headerVerification.data.alg !== jwtHeader.alg) {
1957
- console.debug("Invalid JWT: invalid header alg", parsedHeader);
1958
- return null;
1959
- }
1960
- } catch (err) {
1961
- console.debug("Invalid JWT: could not parse header", err);
1962
- return null;
1980
+ parsedHeader = JSON.parse(Buffer.from(headerBase64, "base64").toString("utf8"));
1981
+ } catch {
1982
+ return failure("malformed", "could not parse header");
1963
1983
  }
1964
- if (secretKey) {
1965
- const signature = crypto__default["default"].createHmac("sha256", secretKey).update(`${headerBase64}.${payloadBase64}`).digest("base64");
1966
- if (signature !== signatureBase64) {
1967
- console.debug("Invalid JWT: invalid signature");
1968
- return null;
1969
- }
1984
+ const headerVerification = JwtHeaderSchema.safeParse(parsedHeader);
1985
+ if (!headerVerification.success) {
1986
+ // NOTE: JwtHeaderSchema pins alg to HS256, so `alg: "none"` and every other
1987
+ // algorithm-confusion header is rejected here, before we look at the
1988
+ // signature at all.
1989
+ return failure("malformed", "unsupported header");
1970
1990
  }
1991
+ return null;
1992
+ }
1993
+ function parsePayload(payloadBase64) {
1994
+ let parsedPayload;
1971
1995
  try {
1972
- const parsedPayload = JSON.parse(Buffer.from(payloadBase64, "base64").toString("utf8"));
1973
- return parsedPayload;
1974
- } catch (err) {
1975
- console.debug("Invalid JWT: could not parse payload", err);
1976
- return null;
1996
+ parsedPayload = JSON.parse(Buffer.from(payloadBase64, "base64").toString("utf8"));
1997
+ } catch {
1998
+ return failure("malformed", "could not parse payload");
1977
1999
  }
2000
+ const payloadVerification = JwtPayloadSchema.safeParse(parsedPayload);
2001
+ if (!payloadVerification.success) {
2002
+ return failure("malformed", "payload is not a Val JWT payload");
2003
+ }
2004
+ const payload = payloadVerification.data;
2005
+ const nowSeconds = Math.floor(Date.now() / 1000);
2006
+ if (payload.exp + CLOCK_SKEW_LEEWAY_SECONDS <= nowSeconds) {
2007
+ return failure("expired", "token expired");
2008
+ }
2009
+ return {
2010
+ success: true,
2011
+ data: payload
2012
+ };
2013
+ }
2014
+
2015
+ /**
2016
+ * Verify a JWT that we issued with {@link encodeJwt}, and return its payload.
2017
+ *
2018
+ * Checks, in order: the segment count, the header (`alg` is pinned to HS256, so
2019
+ * algorithm confusion is rejected before the signature is looked at), the HMAC
2020
+ * (compared in constant time), the payload shape, and `exp`.
2021
+ *
2022
+ * There is deliberately no way to make this skip the signature check: the
2023
+ * secret is required, and an empty one is a failure rather than a token that
2024
+ * validates against `HMAC("")`. Use {@link decodeJwtWithoutVerifying} - and read
2025
+ * what it says - when you genuinely do not hold the signing key.
2026
+ */
2027
+ function verifyJwt(token, secretKey) {
2028
+ if (!secretKey) {
2029
+ return failure("missing-secret", "no secret key was provided");
2030
+ }
2031
+ const segments = splitToken(token);
2032
+ if (!segments) {
2033
+ return failure("malformed", "format is not exactly {header}.{payload}.{signature}");
2034
+ }
2035
+ const {
2036
+ headerBase64,
2037
+ payloadBase64,
2038
+ signatureBase64
2039
+ } = segments;
2040
+ const headerFailure = verifyHeader(headerBase64);
2041
+ if (headerFailure) {
2042
+ return headerFailure;
2043
+ }
2044
+ const expectedSignature = crypto__default["default"].createHmac("sha256", secretKey).update(`${headerBase64}.${payloadBase64}`).digest();
2045
+ const actualSignature = Buffer.from(signatureBase64, "base64");
2046
+ // NOTE: the length check is what makes timingSafeEqual usable at all (it
2047
+ // throws on a length mismatch), and the length of an HMAC-SHA256 is public.
2048
+ if (actualSignature.length !== expectedSignature.length || !crypto__default["default"].timingSafeEqual(actualSignature, expectedSignature)) {
2049
+ return failure("invalid-signature", "signature does not match");
2050
+ }
2051
+ return parsePayload(payloadBase64);
2052
+ }
2053
+
2054
+ /**
2055
+ * Parse a JWT's payload **without checking its signature**.
2056
+ *
2057
+ * The header, the payload shape and `exp` are still validated, but nothing here
2058
+ * establishes that the token was issued by anyone in particular. Only use this
2059
+ * for a token whose authenticity is already established by the channel it
2060
+ * arrived on - the app token we fetch from val.build over an api-key
2061
+ * authenticated HTTPS request is the one such case. Anything that arrives from
2062
+ * a browser (a cookie, a header, a query parameter) must go through
2063
+ * {@link verifyJwt}.
2064
+ */
2065
+ function decodeJwtWithoutVerifying(token) {
2066
+ const segments = splitToken(token);
2067
+ if (!segments) {
2068
+ return failure("malformed", "format is not exactly {header}.{payload}.{signature}");
2069
+ }
2070
+ const headerFailure = verifyHeader(segments.headerBase64);
2071
+ if (headerFailure) {
2072
+ return headerFailure;
2073
+ }
2074
+ return parsePayload(segments.payloadBase64);
1978
2075
  }
1979
2076
  function getExpire() {
1980
2077
  return Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 4; // 4 days
@@ -2610,8 +2707,8 @@ class ValOps {
2610
2707
  * instances in the browser and would otherwise get no previews at all. See
2611
2708
  * #470.
2612
2709
  *
2613
- * A string's `render` needs none of this — it is static config that travels
2614
- * with the serialized schema.
2710
+ * A `render`, and a string's `multiline`, need none of this — they are static
2711
+ * config that travels with the serialized schema.
2615
2712
  */
2616
2713
  async getPreviews(schemas, sources) {
2617
2714
  const previews = {};
@@ -7618,7 +7715,20 @@ const ValServer = (valModules, options, callbacks) => {
7618
7715
  }).then(async res => {
7619
7716
  if (res.status === 200) {
7620
7717
  const token = await res.text();
7621
- const verification = ValAppJwtPayload.safeParse(decodeJwt(token));
7718
+ // NOTE: this token is signed by val.build with a key we do not
7719
+ // hold, so we cannot verify it. What stands in for a signature check
7720
+ // is where it came from: the POST above, authenticated with the
7721
+ // project's api key, to the configured `valBuildUrl`. It is not user
7722
+ // input - but it is only as trustworthy as that host and that
7723
+ // channel, and `valBuildUrl` is overridable (`opts.valBuildUrl`,
7724
+ // `VAL_BUILD_URL`) and not required to be https. Point it at a plain
7725
+ // http host and this payload - which is re-signed into the session
7726
+ // cookie below - is whatever the network says it is.
7727
+ const decoded = decodeJwtWithoutVerifying(token);
7728
+ if (!decoded.success) {
7729
+ return null;
7730
+ }
7731
+ const verification = ValAppJwtPayload.safeParse(decoded.data);
7622
7732
  if (!verification.success) {
7623
7733
  return null;
7624
7734
  }
@@ -7650,8 +7760,8 @@ const ValServer = (valModules, options, callbacks) => {
7650
7760
  }
7651
7761
  }
7652
7762
  if (typeof cookie === "string") {
7653
- const decodedToken = decodeJwt(cookie, options.valSecret);
7654
- if (!decodedToken) {
7763
+ const verifiedToken = verifyJwt(cookie, options.valSecret);
7764
+ if (!verifiedToken.success) {
7655
7765
  if (serverOps instanceof ValOpsFS) {
7656
7766
  return {
7657
7767
  error: null,
@@ -7659,10 +7769,10 @@ const ValServer = (valModules, options, callbacks) => {
7659
7769
  };
7660
7770
  }
7661
7771
  return {
7662
- error: "Could not verify session (invalid token). You will need to login again."
7772
+ error: sessionErrorMessage(verifiedToken.reason)
7663
7773
  };
7664
7774
  }
7665
- const verification = IntegratedServerJwtPayload.safeParse(decodedToken);
7775
+ const verification = IntegratedServerJwtPayload.safeParse(verifiedToken.data);
7666
7776
  if (!verification.success) {
7667
7777
  if (serverOps instanceof ValOpsFS) {
7668
7778
  return {
@@ -7671,7 +7781,7 @@ const ValServer = (valModules, options, callbacks) => {
7671
7781
  };
7672
7782
  }
7673
7783
  return {
7674
- error: "Session invalid or, most likely, expired. You will need to login again."
7784
+ error: "Session invalid. You will need to login again."
7675
7785
  };
7676
7786
  }
7677
7787
  return {
@@ -10151,25 +10261,41 @@ const IntegratedServerJwtPayload = z.z.object({
10151
10261
  org: z.z.string(),
10152
10262
  project: z.z.string()
10153
10263
  });
10264
+ /**
10265
+ * The message we show the user when their session cookie does not verify. The
10266
+ * distinction matters: an expired session is normal and self-explanatory, while
10267
+ * a bad signature means the cookie was tampered with or `VAL_SECRET` changed.
10268
+ */
10269
+ function sessionErrorMessage(reason) {
10270
+ switch (reason) {
10271
+ case "expired":
10272
+ return "Session expired. You will need to login again.";
10273
+ case "malformed":
10274
+ case "invalid-signature":
10275
+ return "Could not verify session (invalid token). You will need to login again.";
10276
+ case "missing-secret":
10277
+ return "Setup is not correct: secret is missing";
10278
+ }
10279
+ }
10154
10280
  async function withAuth(secret, cookies, errorMessageType, handler) {
10155
10281
  const cookie = cookies[internal.VAL_SESSION_COOKIE];
10156
10282
  if (typeof cookie === "string") {
10157
- const decodedToken = decodeJwt(cookie, secret);
10158
- if (!decodedToken) {
10283
+ const verifiedToken = verifyJwt(cookie, secret);
10284
+ if (!verifiedToken.success) {
10159
10285
  return {
10160
10286
  status: 401,
10161
10287
  json: {
10162
- message: "Could not verify session. You will need to login again.",
10163
- details: "Invalid token"
10288
+ message: sessionErrorMessage(verifiedToken.reason),
10289
+ details: verifiedToken.reason
10164
10290
  }
10165
10291
  };
10166
10292
  }
10167
- const verification = IntegratedServerJwtPayload.safeParse(decodedToken);
10293
+ const verification = IntegratedServerJwtPayload.safeParse(verifiedToken.data);
10168
10294
  if (!verification.success) {
10169
10295
  return {
10170
10296
  status: 401,
10171
10297
  json: {
10172
- message: "Session invalid or, most likely, expired. You will need to login again.",
10298
+ message: "Session invalid. You will need to login again.",
10173
10299
  details: zodValidationError.fromError(verification.error).toString()
10174
10300
  }
10175
10301
  };
@@ -12862,7 +12988,7 @@ exports.createValApiRouter = createValApiRouter;
12862
12988
  exports.createValModuleFileInspector = createValModuleFileInspector;
12863
12989
  exports.createValServer = createValServer;
12864
12990
  exports.currentFixHandlers = currentFixHandlers;
12865
- exports.decodeJwt = decodeJwt;
12991
+ exports.decodeJwtWithoutVerifying = decodeJwtWithoutVerifying;
12866
12992
  exports.describePatchStoreProblems = describePatchStoreProblems;
12867
12993
  exports.downloadFileFromRemote = downloadFileFromRemote;
12868
12994
  exports.encodeJwt = encodeJwt;
@@ -12904,3 +13030,4 @@ exports.safeReadGit = safeReadGit;
12904
13030
  exports.startValLogin = startValLogin;
12905
13031
  exports.uploadRemoteFile = uploadRemoteFile;
12906
13032
  exports.validateMetadata = validateMetadata;
13033
+ exports.verifyJwt = verifyJwt;
@@ -1902,45 +1902,142 @@ class Service {
1902
1902
  }
1903
1903
  }
1904
1904
 
1905
- function decodeJwt(token, secretKey) {
1905
+ const JwtPayloadSchema = z.object({
1906
+ sub: z.string(),
1907
+ exp: z.number(),
1908
+ org: z.string(),
1909
+ project: z.string()
1910
+ })
1911
+ // NOTE: passthrough, because the session cookie payload carries more than
1912
+ // this (the val.build token, notably). This is the minimum every Val JWT has.
1913
+ .passthrough();
1914
+ /**
1915
+ * `exp` is an absolute Unix timestamp: a token is rejected once that moment has
1916
+ * passed, with this much slack to absorb clock drift between whoever signed the
1917
+ * token and us.
1918
+ */
1919
+ const CLOCK_SKEW_LEEWAY_SECONDS = 60;
1920
+ function failure(reason, message) {
1921
+ // NOTE: deliberately never logs the token itself - it is a credential, and
1922
+ // anything we print here ends up in the host's request logs.
1923
+ console.debug(`Invalid JWT (${reason}): ${message}`);
1924
+ return {
1925
+ success: false,
1926
+ reason,
1927
+ message
1928
+ };
1929
+ }
1930
+ function splitToken(token) {
1906
1931
  const [headerBase64, payloadBase64, signatureBase64, ...rest] = token.split(".");
1907
1932
  if (!headerBase64 || !payloadBase64 || !signatureBase64 || rest.length > 0) {
1908
- console.debug("Invalid JWT: format is not exactly {header}.{payload}.{signature}", token);
1909
1933
  return null;
1910
1934
  }
1935
+ return {
1936
+ headerBase64,
1937
+ payloadBase64,
1938
+ signatureBase64
1939
+ };
1940
+ }
1941
+
1942
+ /** Returns the failure if the header is not one of ours, or null if it is fine. */
1943
+ function verifyHeader(headerBase64) {
1944
+ let parsedHeader;
1911
1945
  try {
1912
- const parsedHeader = JSON.parse(Buffer.from(headerBase64, "base64").toString("utf8"));
1913
- const headerVerification = JwtHeaderSchema.safeParse(parsedHeader);
1914
- if (!headerVerification.success) {
1915
- console.debug("Invalid JWT: invalid header", parsedHeader);
1916
- return null;
1917
- }
1918
- if (headerVerification.data.typ !== jwtHeader.typ) {
1919
- console.debug("Invalid JWT: invalid header typ", parsedHeader);
1920
- return null;
1921
- }
1922
- if (headerVerification.data.alg !== jwtHeader.alg) {
1923
- console.debug("Invalid JWT: invalid header alg", parsedHeader);
1924
- return null;
1925
- }
1926
- } catch (err) {
1927
- console.debug("Invalid JWT: could not parse header", err);
1928
- return null;
1946
+ parsedHeader = JSON.parse(Buffer.from(headerBase64, "base64").toString("utf8"));
1947
+ } catch {
1948
+ return failure("malformed", "could not parse header");
1929
1949
  }
1930
- if (secretKey) {
1931
- const signature = crypto$1.createHmac("sha256", secretKey).update(`${headerBase64}.${payloadBase64}`).digest("base64");
1932
- if (signature !== signatureBase64) {
1933
- console.debug("Invalid JWT: invalid signature");
1934
- return null;
1935
- }
1950
+ const headerVerification = JwtHeaderSchema.safeParse(parsedHeader);
1951
+ if (!headerVerification.success) {
1952
+ // NOTE: JwtHeaderSchema pins alg to HS256, so `alg: "none"` and every other
1953
+ // algorithm-confusion header is rejected here, before we look at the
1954
+ // signature at all.
1955
+ return failure("malformed", "unsupported header");
1936
1956
  }
1957
+ return null;
1958
+ }
1959
+ function parsePayload(payloadBase64) {
1960
+ let parsedPayload;
1937
1961
  try {
1938
- const parsedPayload = JSON.parse(Buffer.from(payloadBase64, "base64").toString("utf8"));
1939
- return parsedPayload;
1940
- } catch (err) {
1941
- console.debug("Invalid JWT: could not parse payload", err);
1942
- return null;
1962
+ parsedPayload = JSON.parse(Buffer.from(payloadBase64, "base64").toString("utf8"));
1963
+ } catch {
1964
+ return failure("malformed", "could not parse payload");
1943
1965
  }
1966
+ const payloadVerification = JwtPayloadSchema.safeParse(parsedPayload);
1967
+ if (!payloadVerification.success) {
1968
+ return failure("malformed", "payload is not a Val JWT payload");
1969
+ }
1970
+ const payload = payloadVerification.data;
1971
+ const nowSeconds = Math.floor(Date.now() / 1000);
1972
+ if (payload.exp + CLOCK_SKEW_LEEWAY_SECONDS <= nowSeconds) {
1973
+ return failure("expired", "token expired");
1974
+ }
1975
+ return {
1976
+ success: true,
1977
+ data: payload
1978
+ };
1979
+ }
1980
+
1981
+ /**
1982
+ * Verify a JWT that we issued with {@link encodeJwt}, and return its payload.
1983
+ *
1984
+ * Checks, in order: the segment count, the header (`alg` is pinned to HS256, so
1985
+ * algorithm confusion is rejected before the signature is looked at), the HMAC
1986
+ * (compared in constant time), the payload shape, and `exp`.
1987
+ *
1988
+ * There is deliberately no way to make this skip the signature check: the
1989
+ * secret is required, and an empty one is a failure rather than a token that
1990
+ * validates against `HMAC("")`. Use {@link decodeJwtWithoutVerifying} - and read
1991
+ * what it says - when you genuinely do not hold the signing key.
1992
+ */
1993
+ function verifyJwt(token, secretKey) {
1994
+ if (!secretKey) {
1995
+ return failure("missing-secret", "no secret key was provided");
1996
+ }
1997
+ const segments = splitToken(token);
1998
+ if (!segments) {
1999
+ return failure("malformed", "format is not exactly {header}.{payload}.{signature}");
2000
+ }
2001
+ const {
2002
+ headerBase64,
2003
+ payloadBase64,
2004
+ signatureBase64
2005
+ } = segments;
2006
+ const headerFailure = verifyHeader(headerBase64);
2007
+ if (headerFailure) {
2008
+ return headerFailure;
2009
+ }
2010
+ const expectedSignature = crypto$1.createHmac("sha256", secretKey).update(`${headerBase64}.${payloadBase64}`).digest();
2011
+ const actualSignature = Buffer.from(signatureBase64, "base64");
2012
+ // NOTE: the length check is what makes timingSafeEqual usable at all (it
2013
+ // throws on a length mismatch), and the length of an HMAC-SHA256 is public.
2014
+ if (actualSignature.length !== expectedSignature.length || !crypto$1.timingSafeEqual(actualSignature, expectedSignature)) {
2015
+ return failure("invalid-signature", "signature does not match");
2016
+ }
2017
+ return parsePayload(payloadBase64);
2018
+ }
2019
+
2020
+ /**
2021
+ * Parse a JWT's payload **without checking its signature**.
2022
+ *
2023
+ * The header, the payload shape and `exp` are still validated, but nothing here
2024
+ * establishes that the token was issued by anyone in particular. Only use this
2025
+ * for a token whose authenticity is already established by the channel it
2026
+ * arrived on - the app token we fetch from val.build over an api-key
2027
+ * authenticated HTTPS request is the one such case. Anything that arrives from
2028
+ * a browser (a cookie, a header, a query parameter) must go through
2029
+ * {@link verifyJwt}.
2030
+ */
2031
+ function decodeJwtWithoutVerifying(token) {
2032
+ const segments = splitToken(token);
2033
+ if (!segments) {
2034
+ return failure("malformed", "format is not exactly {header}.{payload}.{signature}");
2035
+ }
2036
+ const headerFailure = verifyHeader(segments.headerBase64);
2037
+ if (headerFailure) {
2038
+ return headerFailure;
2039
+ }
2040
+ return parsePayload(segments.payloadBase64);
1944
2041
  }
1945
2042
  function getExpire() {
1946
2043
  return Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 4; // 4 days
@@ -2576,8 +2673,8 @@ class ValOps {
2576
2673
  * instances in the browser and would otherwise get no previews at all. See
2577
2674
  * #470.
2578
2675
  *
2579
- * A string's `render` needs none of this — it is static config that travels
2580
- * with the serialized schema.
2676
+ * A `render`, and a string's `multiline`, need none of this — they are static
2677
+ * config that travels with the serialized schema.
2581
2678
  */
2582
2679
  async getPreviews(schemas, sources) {
2583
2680
  const previews = {};
@@ -7584,7 +7681,20 @@ const ValServer = (valModules, options, callbacks) => {
7584
7681
  }).then(async res => {
7585
7682
  if (res.status === 200) {
7586
7683
  const token = await res.text();
7587
- const verification = ValAppJwtPayload.safeParse(decodeJwt(token));
7684
+ // NOTE: this token is signed by val.build with a key we do not
7685
+ // hold, so we cannot verify it. What stands in for a signature check
7686
+ // is where it came from: the POST above, authenticated with the
7687
+ // project's api key, to the configured `valBuildUrl`. It is not user
7688
+ // input - but it is only as trustworthy as that host and that
7689
+ // channel, and `valBuildUrl` is overridable (`opts.valBuildUrl`,
7690
+ // `VAL_BUILD_URL`) and not required to be https. Point it at a plain
7691
+ // http host and this payload - which is re-signed into the session
7692
+ // cookie below - is whatever the network says it is.
7693
+ const decoded = decodeJwtWithoutVerifying(token);
7694
+ if (!decoded.success) {
7695
+ return null;
7696
+ }
7697
+ const verification = ValAppJwtPayload.safeParse(decoded.data);
7588
7698
  if (!verification.success) {
7589
7699
  return null;
7590
7700
  }
@@ -7616,8 +7726,8 @@ const ValServer = (valModules, options, callbacks) => {
7616
7726
  }
7617
7727
  }
7618
7728
  if (typeof cookie === "string") {
7619
- const decodedToken = decodeJwt(cookie, options.valSecret);
7620
- if (!decodedToken) {
7729
+ const verifiedToken = verifyJwt(cookie, options.valSecret);
7730
+ if (!verifiedToken.success) {
7621
7731
  if (serverOps instanceof ValOpsFS) {
7622
7732
  return {
7623
7733
  error: null,
@@ -7625,10 +7735,10 @@ const ValServer = (valModules, options, callbacks) => {
7625
7735
  };
7626
7736
  }
7627
7737
  return {
7628
- error: "Could not verify session (invalid token). You will need to login again."
7738
+ error: sessionErrorMessage(verifiedToken.reason)
7629
7739
  };
7630
7740
  }
7631
- const verification = IntegratedServerJwtPayload.safeParse(decodedToken);
7741
+ const verification = IntegratedServerJwtPayload.safeParse(verifiedToken.data);
7632
7742
  if (!verification.success) {
7633
7743
  if (serverOps instanceof ValOpsFS) {
7634
7744
  return {
@@ -7637,7 +7747,7 @@ const ValServer = (valModules, options, callbacks) => {
7637
7747
  };
7638
7748
  }
7639
7749
  return {
7640
- error: "Session invalid or, most likely, expired. You will need to login again."
7750
+ error: "Session invalid. You will need to login again."
7641
7751
  };
7642
7752
  }
7643
7753
  return {
@@ -10117,25 +10227,41 @@ const IntegratedServerJwtPayload = z.object({
10117
10227
  org: z.string(),
10118
10228
  project: z.string()
10119
10229
  });
10230
+ /**
10231
+ * The message we show the user when their session cookie does not verify. The
10232
+ * distinction matters: an expired session is normal and self-explanatory, while
10233
+ * a bad signature means the cookie was tampered with or `VAL_SECRET` changed.
10234
+ */
10235
+ function sessionErrorMessage(reason) {
10236
+ switch (reason) {
10237
+ case "expired":
10238
+ return "Session expired. You will need to login again.";
10239
+ case "malformed":
10240
+ case "invalid-signature":
10241
+ return "Could not verify session (invalid token). You will need to login again.";
10242
+ case "missing-secret":
10243
+ return "Setup is not correct: secret is missing";
10244
+ }
10245
+ }
10120
10246
  async function withAuth(secret, cookies, errorMessageType, handler) {
10121
10247
  const cookie = cookies[VAL_SESSION_COOKIE];
10122
10248
  if (typeof cookie === "string") {
10123
- const decodedToken = decodeJwt(cookie, secret);
10124
- if (!decodedToken) {
10249
+ const verifiedToken = verifyJwt(cookie, secret);
10250
+ if (!verifiedToken.success) {
10125
10251
  return {
10126
10252
  status: 401,
10127
10253
  json: {
10128
- message: "Could not verify session. You will need to login again.",
10129
- details: "Invalid token"
10254
+ message: sessionErrorMessage(verifiedToken.reason),
10255
+ details: verifiedToken.reason
10130
10256
  }
10131
10257
  };
10132
10258
  }
10133
- const verification = IntegratedServerJwtPayload.safeParse(decodedToken);
10259
+ const verification = IntegratedServerJwtPayload.safeParse(verifiedToken.data);
10134
10260
  if (!verification.success) {
10135
10261
  return {
10136
10262
  status: 401,
10137
10263
  json: {
10138
- message: "Session invalid or, most likely, expired. You will need to login again.",
10264
+ message: "Session invalid. You will need to login again.",
10139
10265
  details: fromError(verification.error).toString()
10140
10266
  }
10141
10267
  };
@@ -12800,4 +12926,4 @@ function readCapturedReport(snapshotDir) {
12800
12926
  return JSON.parse(fs.readFileSync(reportPath, "utf-8"));
12801
12927
  }
12802
12928
 
12803
- export { DEFAULT_LOGIN_HOST, DEFAULT_LOGIN_MAX_DURATION, DEFAULT_LOGIN_POLL_INTERVAL, Service, ValFSHost, ValLoginError, ValModuleLoader, ValOpsFS, ValOpsHttp, ValSourceFileHandler, analyzeValModule, awaitValLoginConfirmation, checkRemoteRef, classifyJsonValuesOp, compareWithCapturedReport, createDefaultValFSHost, createFixPatch, createJsonEntryPathMap, createModulePathMap, createService, createValApiRouter, createValModuleFileInspector, createValServer, currentFixHandlers, decodeJwt, describePatchStoreProblems, downloadFileFromRemote, encodeJwt, evalValConfigFile, extractFileMetadata, extractImageMetadata, extractJsonValuesEntry, findAndEvalValConfigFile, findJsonEntryFilePath, fixHandlers, formatPatchSourceError, formatSyntaxErrorTree, getCachedRemoteFileDir, getCachedRemoteFilePath, getCompilerOptions, getExpire, getFileExt, getModulePathRange, getPersonalAccessTokenPath, getSettings, getValidationErrorFileRef, handleCheckAllFiles, handleFileMetadata, handleJsonValuesExtractEntry, handleRemoteFileCheck, handleRemoteFileDownload, handleRemoteFileUpload, handleRemoteGalleryFileUpload, handleUniqueFolderCheck, loadValModules, parsePersonalAccessTokenFile, patchSourceFile, persistPersonalAccessToken, readCapturedReport, readPatchStore, rebaseContentOp, replaySnapshot, safeReadGit, startValLogin, uploadRemoteFile, validateMetadata };
12929
+ export { DEFAULT_LOGIN_HOST, DEFAULT_LOGIN_MAX_DURATION, DEFAULT_LOGIN_POLL_INTERVAL, Service, ValFSHost, ValLoginError, ValModuleLoader, ValOpsFS, ValOpsHttp, ValSourceFileHandler, analyzeValModule, awaitValLoginConfirmation, checkRemoteRef, classifyJsonValuesOp, compareWithCapturedReport, createDefaultValFSHost, createFixPatch, createJsonEntryPathMap, createModulePathMap, createService, createValApiRouter, createValModuleFileInspector, createValServer, currentFixHandlers, decodeJwtWithoutVerifying, describePatchStoreProblems, downloadFileFromRemote, encodeJwt, evalValConfigFile, extractFileMetadata, extractImageMetadata, extractJsonValuesEntry, findAndEvalValConfigFile, findJsonEntryFilePath, fixHandlers, formatPatchSourceError, formatSyntaxErrorTree, getCachedRemoteFileDir, getCachedRemoteFilePath, getCompilerOptions, getExpire, getFileExt, getModulePathRange, getPersonalAccessTokenPath, getSettings, getValidationErrorFileRef, handleCheckAllFiles, handleFileMetadata, handleJsonValuesExtractEntry, handleRemoteFileCheck, handleRemoteFileDownload, handleRemoteFileUpload, handleRemoteGalleryFileUpload, handleUniqueFolderCheck, loadValModules, parsePersonalAccessTokenFile, patchSourceFile, persistPersonalAccessToken, readCapturedReport, readPatchStore, rebaseContentOp, replaySnapshot, safeReadGit, startValLogin, uploadRemoteFile, validateMetadata, verifyJwt };
package/package.json CHANGED
@@ -16,7 +16,7 @@
16
16
  "./package.json": "./package.json"
17
17
  },
18
18
  "types": "dist/valbuild-server.cjs.d.ts",
19
- "version": "0.110.1",
19
+ "version": "0.112.0",
20
20
  "devDependencies": {
21
21
  "@prettier/sync": "^0.6.1",
22
22
  "@types/jest": "^30.0.0"
@@ -29,9 +29,9 @@
29
29
  "typescript": "^6.0.3",
30
30
  "zod": "^4.4.3",
31
31
  "zod-validation-error": "^5.0.0",
32
- "@valbuild/core": "0.110.0",
33
- "@valbuild/shared": "0.110.0",
34
- "@valbuild/ui": "0.110.1"
32
+ "@valbuild/shared": "0.111.0",
33
+ "@valbuild/ui": "0.111.0",
34
+ "@valbuild/core": "0.111.0"
35
35
  },
36
36
  "engines": {
37
37
  "node": "^20.19.0 || >=22"