@stackframe/stack-shared 2.8.32 → 2.8.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/config/schema.d.mts +184 -231
  3. package/dist/config/schema.d.ts +184 -231
  4. package/dist/config/schema.js +39 -31
  5. package/dist/config/schema.js.map +1 -1
  6. package/dist/esm/config/schema.js +39 -31
  7. package/dist/esm/config/schema.js.map +1 -1
  8. package/dist/esm/helpers/production-mode.js +2 -1
  9. package/dist/esm/helpers/production-mode.js.map +1 -1
  10. package/dist/esm/interface/admin-interface.js +28 -8
  11. package/dist/esm/interface/admin-interface.js.map +1 -1
  12. package/dist/esm/interface/client-interface.js +29 -12
  13. package/dist/esm/interface/client-interface.js.map +1 -1
  14. package/dist/esm/interface/crud/users.js +1 -1
  15. package/dist/esm/interface/crud/users.js.map +1 -1
  16. package/dist/esm/interface/server-interface.js +22 -10
  17. package/dist/esm/interface/server-interface.js.map +1 -1
  18. package/dist/esm/known-errors.js +10 -0
  19. package/dist/esm/known-errors.js.map +1 -1
  20. package/dist/esm/schema-fields.js +52 -9
  21. package/dist/esm/schema-fields.js.map +1 -1
  22. package/dist/esm/utils/currencies.js +0 -38
  23. package/dist/esm/utils/currencies.js.map +1 -1
  24. package/dist/esm/utils/currency-constants.js +42 -0
  25. package/dist/esm/utils/currency-constants.js.map +1 -0
  26. package/dist/esm/utils/dates.js +30 -0
  27. package/dist/esm/utils/dates.js.map +1 -1
  28. package/dist/esm/utils/jwt.js +42 -37
  29. package/dist/esm/utils/jwt.js.map +1 -1
  30. package/dist/esm/utils/strings.js +3 -0
  31. package/dist/esm/utils/strings.js.map +1 -1
  32. package/dist/esm/utils/types.js.map +1 -1
  33. package/dist/esm/utils/urls.js +54 -0
  34. package/dist/esm/utils/urls.js.map +1 -1
  35. package/dist/helpers/password.d.mts +1 -1
  36. package/dist/helpers/password.d.ts +1 -1
  37. package/dist/helpers/production-mode.js +2 -1
  38. package/dist/helpers/production-mode.js.map +1 -1
  39. package/dist/index.d.mts +3 -3
  40. package/dist/index.d.ts +3 -3
  41. package/dist/interface/admin-interface.d.mts +15 -7
  42. package/dist/interface/admin-interface.d.ts +15 -7
  43. package/dist/interface/admin-interface.js +28 -8
  44. package/dist/interface/admin-interface.js.map +1 -1
  45. package/dist/interface/client-interface.d.mts +17 -16
  46. package/dist/interface/client-interface.d.ts +17 -16
  47. package/dist/interface/client-interface.js +29 -12
  48. package/dist/interface/client-interface.js.map +1 -1
  49. package/dist/interface/crud/current-user.d.mts +1 -1
  50. package/dist/interface/crud/current-user.d.ts +1 -1
  51. package/dist/interface/crud/project-api-keys.d.mts +4 -4
  52. package/dist/interface/crud/project-api-keys.d.ts +4 -4
  53. package/dist/interface/crud/team-member-profiles.d.mts +2 -2
  54. package/dist/interface/crud/team-member-profiles.d.ts +2 -2
  55. package/dist/interface/crud/users.d.mts +4 -4
  56. package/dist/interface/crud/users.d.ts +4 -4
  57. package/dist/interface/crud/users.js +1 -1
  58. package/dist/interface/crud/users.js.map +1 -1
  59. package/dist/interface/server-interface.d.mts +14 -4
  60. package/dist/interface/server-interface.d.ts +14 -4
  61. package/dist/interface/server-interface.js +22 -10
  62. package/dist/interface/server-interface.js.map +1 -1
  63. package/dist/known-errors.d.mts +6 -3
  64. package/dist/known-errors.d.ts +6 -3
  65. package/dist/known-errors.js +10 -0
  66. package/dist/known-errors.js.map +1 -1
  67. package/dist/schema-fields.d.mts +28 -8
  68. package/dist/schema-fields.d.ts +28 -8
  69. package/dist/schema-fields.js +56 -11
  70. package/dist/schema-fields.js.map +1 -1
  71. package/dist/utils/currencies.d.mts +3 -36
  72. package/dist/utils/currencies.d.ts +3 -36
  73. package/dist/utils/currencies.js +0 -39
  74. package/dist/utils/currencies.js.map +1 -1
  75. package/dist/utils/currency-constants.d.mts +37 -0
  76. package/dist/utils/currency-constants.d.ts +37 -0
  77. package/dist/utils/currency-constants.js +67 -0
  78. package/dist/utils/currency-constants.js.map +1 -0
  79. package/dist/utils/dates.d.mts +3 -1
  80. package/dist/utils/dates.d.ts +3 -1
  81. package/dist/utils/dates.js +32 -0
  82. package/dist/utils/dates.js.map +1 -1
  83. package/dist/utils/jwt.d.mts +11 -11
  84. package/dist/utils/jwt.d.ts +11 -11
  85. package/dist/utils/jwt.js +44 -42
  86. package/dist/utils/jwt.js.map +1 -1
  87. package/dist/utils/strings.js +3 -0
  88. package/dist/utils/strings.js.map +1 -1
  89. package/dist/utils/types.d.mts +16 -5
  90. package/dist/utils/types.d.ts +16 -5
  91. package/dist/utils/types.js.map +1 -1
  92. package/dist/utils/urls.d.mts +3 -1
  93. package/dist/utils/urls.d.ts +3 -1
  94. package/dist/utils/urls.js +56 -0
  95. package/dist/utils/urls.js.map +1 -1
  96. package/package.json +1 -1
@@ -20,49 +20,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/utils/currencies.tsx
21
21
  var currencies_exports = {};
22
22
  __export(currencies_exports, {
23
- SUPPORTED_CURRENCIES: () => SUPPORTED_CURRENCIES,
24
23
  moneyAmountToStripeUnits: () => moneyAmountToStripeUnits
25
24
  });
26
25
  module.exports = __toCommonJS(currencies_exports);
27
26
  var import_schema_fields = require("../schema-fields.js");
28
27
  var import_errors = require("./errors.js");
29
- var SUPPORTED_CURRENCIES = [
30
- {
31
- code: "USD",
32
- decimals: 2,
33
- stripeDecimals: 2
34
- },
35
- {
36
- code: "EUR",
37
- decimals: 2,
38
- stripeDecimals: 2
39
- },
40
- {
41
- code: "GBP",
42
- decimals: 2,
43
- stripeDecimals: 2
44
- },
45
- {
46
- code: "JPY",
47
- decimals: 0,
48
- stripeDecimals: 0
49
- },
50
- {
51
- code: "INR",
52
- decimals: 2,
53
- stripeDecimals: 2
54
- },
55
- {
56
- code: "AUD",
57
- decimals: 2,
58
- stripeDecimals: 2
59
- },
60
- {
61
- code: "CAD",
62
- decimals: 2,
63
- stripeDecimals: 2
64
- }
65
- ];
66
28
  function moneyAmountToStripeUnits(amount, currency) {
67
29
  const validated = (0, import_schema_fields.moneyAmountSchema)(currency).defined().validateSync(amount);
68
30
  if (currency.stripeDecimals !== currency.decimals) {
@@ -72,7 +34,6 @@ function moneyAmountToStripeUnits(amount, currency) {
72
34
  }
73
35
  // Annotate the CommonJS export names for ESM import in node:
74
36
  0 && (module.exports = {
75
- SUPPORTED_CURRENCIES,
76
37
  moneyAmountToStripeUnits
77
38
  });
78
39
  //# sourceMappingURL=currencies.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/currencies.tsx"],"sourcesContent":["import { moneyAmountSchema } from \"../schema-fields\";\nimport { StackAssertionError } from \"./errors\";\n\nexport type Currency = {\n code: Uppercase<string>,\n decimals: number,\n stripeDecimals: number,\n};\n\nexport type SupportedCurrency = (typeof SUPPORTED_CURRENCIES)[number];\nexport const SUPPORTED_CURRENCIES = [\n {\n code: 'USD',\n decimals: 2,\n stripeDecimals: 2,\n },\n {\n code: 'EUR',\n decimals: 2,\n stripeDecimals: 2,\n },\n {\n code: 'GBP',\n decimals: 2,\n stripeDecimals: 2,\n },\n {\n code: 'JPY',\n decimals: 0,\n stripeDecimals: 0,\n },\n {\n code: 'INR',\n decimals: 2,\n stripeDecimals: 2,\n },\n {\n code: 'AUD',\n decimals: 2,\n stripeDecimals: 2,\n },\n {\n code: 'CAD',\n decimals: 2,\n stripeDecimals: 2,\n },\n] as const satisfies Currency[];\n\nexport type MoneyAmount = `${number}` | `${number}.${number}`;\n\nexport function moneyAmountToStripeUnits(amount: MoneyAmount, currency: Currency): number {\n const validated = moneyAmountSchema(currency).defined().validateSync(amount);\n if (currency.stripeDecimals !== currency.decimals) {\n throw new StackAssertionError(\"unimplemented: TODO support different decimal configurations\");\n }\n\n return Number.parseInt(validated.replace('.', ''), 10);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,2BAAkC;AAClC,oBAAoC;AAS7B,IAAM,uBAAuB;AAAA,EAClC;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,gBAAgB;AAAA,EAClB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,gBAAgB;AAAA,EAClB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,gBAAgB;AAAA,EAClB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,gBAAgB;AAAA,EAClB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,gBAAgB;AAAA,EAClB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,gBAAgB;AAAA,EAClB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,gBAAgB;AAAA,EAClB;AACF;AAIO,SAAS,yBAAyB,QAAqB,UAA4B;AACxF,QAAM,gBAAY,wCAAkB,QAAQ,EAAE,QAAQ,EAAE,aAAa,MAAM;AAC3E,MAAI,SAAS,mBAAmB,SAAS,UAAU;AACjD,UAAM,IAAI,kCAAoB,8DAA8D;AAAA,EAC9F;AAEA,SAAO,OAAO,SAAS,UAAU,QAAQ,KAAK,EAAE,GAAG,EAAE;AACvD;","names":[]}
1
+ {"version":3,"sources":["../../src/utils/currencies.tsx"],"sourcesContent":["import { moneyAmountSchema } from \"../schema-fields\";\nimport { SUPPORTED_CURRENCIES, type Currency, type MoneyAmount } from \"./currency-constants\";\nimport { StackAssertionError } from \"./errors\";\n\nexport type SupportedCurrency = (typeof SUPPORTED_CURRENCIES)[number];\n\nexport function moneyAmountToStripeUnits(amount: MoneyAmount, currency: Currency): number {\n const validated = moneyAmountSchema(currency).defined().validateSync(amount);\n if (currency.stripeDecimals !== currency.decimals) {\n throw new StackAssertionError(\"unimplemented: TODO support different decimal configurations\");\n }\n\n return Number.parseInt(validated.replace('.', ''), 10);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,2BAAkC;AAElC,oBAAoC;AAI7B,SAAS,yBAAyB,QAAqB,UAA4B;AACxF,QAAM,gBAAY,wCAAkB,QAAQ,EAAE,QAAQ,EAAE,aAAa,MAAM;AAC3E,MAAI,SAAS,mBAAmB,SAAS,UAAU;AACjD,UAAM,IAAI,kCAAoB,8DAA8D;AAAA,EAC9F;AAEA,SAAO,OAAO,SAAS,UAAU,QAAQ,KAAK,EAAE,GAAG,EAAE;AACvD;","names":[]}
@@ -0,0 +1,37 @@
1
+ type MoneyAmount = `${number}` | `${number}.${number}`;
2
+ type Currency = {
3
+ code: Uppercase<string>;
4
+ decimals: number;
5
+ stripeDecimals: number;
6
+ };
7
+ declare const SUPPORTED_CURRENCIES: [{
8
+ readonly code: "USD";
9
+ readonly decimals: 2;
10
+ readonly stripeDecimals: 2;
11
+ }, {
12
+ readonly code: "EUR";
13
+ readonly decimals: 2;
14
+ readonly stripeDecimals: 2;
15
+ }, {
16
+ readonly code: "GBP";
17
+ readonly decimals: 2;
18
+ readonly stripeDecimals: 2;
19
+ }, {
20
+ readonly code: "JPY";
21
+ readonly decimals: 0;
22
+ readonly stripeDecimals: 0;
23
+ }, {
24
+ readonly code: "INR";
25
+ readonly decimals: 2;
26
+ readonly stripeDecimals: 2;
27
+ }, {
28
+ readonly code: "AUD";
29
+ readonly decimals: 2;
30
+ readonly stripeDecimals: 2;
31
+ }, {
32
+ readonly code: "CAD";
33
+ readonly decimals: 2;
34
+ readonly stripeDecimals: 2;
35
+ }];
36
+
37
+ export { type Currency, type MoneyAmount, SUPPORTED_CURRENCIES };
@@ -0,0 +1,37 @@
1
+ type MoneyAmount = `${number}` | `${number}.${number}`;
2
+ type Currency = {
3
+ code: Uppercase<string>;
4
+ decimals: number;
5
+ stripeDecimals: number;
6
+ };
7
+ declare const SUPPORTED_CURRENCIES: [{
8
+ readonly code: "USD";
9
+ readonly decimals: 2;
10
+ readonly stripeDecimals: 2;
11
+ }, {
12
+ readonly code: "EUR";
13
+ readonly decimals: 2;
14
+ readonly stripeDecimals: 2;
15
+ }, {
16
+ readonly code: "GBP";
17
+ readonly decimals: 2;
18
+ readonly stripeDecimals: 2;
19
+ }, {
20
+ readonly code: "JPY";
21
+ readonly decimals: 0;
22
+ readonly stripeDecimals: 0;
23
+ }, {
24
+ readonly code: "INR";
25
+ readonly decimals: 2;
26
+ readonly stripeDecimals: 2;
27
+ }, {
28
+ readonly code: "AUD";
29
+ readonly decimals: 2;
30
+ readonly stripeDecimals: 2;
31
+ }, {
32
+ readonly code: "CAD";
33
+ readonly decimals: 2;
34
+ readonly stripeDecimals: 2;
35
+ }];
36
+
37
+ export { type Currency, type MoneyAmount, SUPPORTED_CURRENCIES };
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/utils/currency-constants.tsx
21
+ var currency_constants_exports = {};
22
+ __export(currency_constants_exports, {
23
+ SUPPORTED_CURRENCIES: () => SUPPORTED_CURRENCIES
24
+ });
25
+ module.exports = __toCommonJS(currency_constants_exports);
26
+ var SUPPORTED_CURRENCIES = [
27
+ {
28
+ code: "USD",
29
+ decimals: 2,
30
+ stripeDecimals: 2
31
+ },
32
+ {
33
+ code: "EUR",
34
+ decimals: 2,
35
+ stripeDecimals: 2
36
+ },
37
+ {
38
+ code: "GBP",
39
+ decimals: 2,
40
+ stripeDecimals: 2
41
+ },
42
+ {
43
+ code: "JPY",
44
+ decimals: 0,
45
+ stripeDecimals: 0
46
+ },
47
+ {
48
+ code: "INR",
49
+ decimals: 2,
50
+ stripeDecimals: 2
51
+ },
52
+ {
53
+ code: "AUD",
54
+ decimals: 2,
55
+ stripeDecimals: 2
56
+ },
57
+ {
58
+ code: "CAD",
59
+ decimals: 2,
60
+ stripeDecimals: 2
61
+ }
62
+ ];
63
+ // Annotate the CommonJS export names for ESM import in node:
64
+ 0 && (module.exports = {
65
+ SUPPORTED_CURRENCIES
66
+ });
67
+ //# sourceMappingURL=currency-constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/currency-constants.tsx"],"sourcesContent":["export type MoneyAmount = `${number}` | `${number}.${number}`;\n\nexport type Currency = {\n code: Uppercase<string>,\n decimals: number,\n stripeDecimals: number,\n};\n\nexport const SUPPORTED_CURRENCIES = [\n {\n code: 'USD',\n decimals: 2,\n stripeDecimals: 2,\n },\n {\n code: 'EUR',\n decimals: 2,\n stripeDecimals: 2,\n },\n {\n code: 'GBP',\n decimals: 2,\n stripeDecimals: 2,\n },\n {\n code: 'JPY',\n decimals: 0,\n stripeDecimals: 0,\n },\n {\n code: 'INR',\n decimals: 2,\n stripeDecimals: 2,\n },\n {\n code: 'AUD',\n decimals: 2,\n stripeDecimals: 2,\n },\n {\n code: 'CAD',\n decimals: 2,\n stripeDecimals: 2,\n },\n] as const satisfies Currency[];\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAQO,IAAM,uBAAuB;AAAA,EAClC;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,gBAAgB;AAAA,EAClB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,gBAAgB;AAAA,EAClB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,gBAAgB;AAAA,EAClB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,gBAAgB;AAAA,EAClB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,gBAAgB;AAAA,EAClB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,gBAAgB;AAAA,EAClB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,gBAAgB;AAAA,EAClB;AACF;","names":[]}
@@ -15,5 +15,7 @@ type Interval = [number, 'millisecond' | 'second' | 'minute' | 'hour' | 'day' |
15
15
  type DayInterval = [number, 'day' | 'week' | 'month' | 'year'];
16
16
  declare function subtractInterval(date: Date, interval: Interval): Date;
17
17
  declare function addInterval(date: Date, interval: Interval): Date;
18
+ declare const FAR_FUTURE_DATE: Date;
19
+ declare function getIntervalsElapsed(anchor: Date, to: Date, repeat: DayInterval): number;
18
20
 
19
- export { type DayInterval, type Interval, addInterval, fromNow, fromNowDetailed, getInputDatetimeLocalString, isWeekend, subtractInterval };
21
+ export { type DayInterval, FAR_FUTURE_DATE, type Interval, addInterval, fromNow, fromNowDetailed, getInputDatetimeLocalString, getIntervalsElapsed, isWeekend, subtractInterval };
@@ -15,5 +15,7 @@ type Interval = [number, 'millisecond' | 'second' | 'minute' | 'hour' | 'day' |
15
15
  type DayInterval = [number, 'day' | 'week' | 'month' | 'year'];
16
16
  declare function subtractInterval(date: Date, interval: Interval): Date;
17
17
  declare function addInterval(date: Date, interval: Interval): Date;
18
+ declare const FAR_FUTURE_DATE: Date;
19
+ declare function getIntervalsElapsed(anchor: Date, to: Date, repeat: DayInterval): number;
18
20
 
19
- export { type DayInterval, type Interval, addInterval, fromNow, fromNowDetailed, getInputDatetimeLocalString, isWeekend, subtractInterval };
21
+ export { type DayInterval, FAR_FUTURE_DATE, type Interval, addInterval, fromNow, fromNowDetailed, getInputDatetimeLocalString, getIntervalsElapsed, isWeekend, subtractInterval };
@@ -20,10 +20,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/utils/dates.tsx
21
21
  var dates_exports = {};
22
22
  __export(dates_exports, {
23
+ FAR_FUTURE_DATE: () => FAR_FUTURE_DATE,
23
24
  addInterval: () => addInterval,
24
25
  fromNow: () => fromNow,
25
26
  fromNowDetailed: () => fromNowDetailed,
26
27
  getInputDatetimeLocalString: () => getInputDatetimeLocalString,
28
+ getIntervalsElapsed: () => getIntervalsElapsed,
27
29
  isWeekend: () => isWeekend,
28
30
  subtractInterval: () => subtractInterval
29
31
  });
@@ -136,12 +138,42 @@ function subtractInterval(date, interval) {
136
138
  function addInterval(date, interval) {
137
139
  return applyInterval(date, 1, interval);
138
140
  }
141
+ var FAR_FUTURE_DATE = /* @__PURE__ */ new Date(864e13);
142
+ function getMsPerDayIntervalUnit(unit) {
143
+ if (unit === "day") {
144
+ return 24 * 60 * 60 * 1e3;
145
+ }
146
+ return 7 * 24 * 60 * 60 * 1e3;
147
+ }
148
+ function getIntervalsElapsed(anchor, to, repeat) {
149
+ const [amount, unit] = repeat;
150
+ if (to <= anchor) return 0;
151
+ if (unit === "day" || unit === "week") {
152
+ const msPerUnit = getMsPerDayIntervalUnit(unit);
153
+ const diffMs = to.getTime() - anchor.getTime();
154
+ return Math.floor(diffMs / (msPerUnit * amount));
155
+ }
156
+ if (["month", "year"].includes(unit)) {
157
+ let count = 0;
158
+ let current = new Date(anchor);
159
+ for (; ; ) {
160
+ const next = addInterval(new Date(current), [amount, unit]);
161
+ if (next > to) break;
162
+ current = next;
163
+ count += 1;
164
+ }
165
+ return count;
166
+ }
167
+ return 0;
168
+ }
139
169
  // Annotate the CommonJS export names for ESM import in node:
140
170
  0 && (module.exports = {
171
+ FAR_FUTURE_DATE,
141
172
  addInterval,
142
173
  fromNow,
143
174
  fromNowDetailed,
144
175
  getInputDatetimeLocalString,
176
+ getIntervalsElapsed,
145
177
  isWeekend,
146
178
  subtractInterval
147
179
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/dates.tsx"],"sourcesContent":["import { intervalSchema } from \"../schema-fields\";\nimport { StackAssertionError } from \"./errors\";\nimport { remainder } from \"./math\";\n\nexport function isWeekend(date: Date): boolean {\n return date.getDay() === 0 || date.getDay() === 6;\n}\n\nundefined?.test(\"isWeekend\", ({ expect }) => {\n // Sunday (day 0)\n expect(isWeekend(new Date(2023, 0, 1))).toBe(true);\n // Saturday (day 6)\n expect(isWeekend(new Date(2023, 0, 7))).toBe(true);\n // Monday (day 1)\n expect(isWeekend(new Date(2023, 0, 2))).toBe(false);\n // Friday (day 5)\n expect(isWeekend(new Date(2023, 0, 6))).toBe(false);\n});\n\nconst agoUnits = [\n [60, 'second'],\n [60, 'minute'],\n [24, 'hour'],\n [7, 'day'],\n [5, 'week'],\n] as const;\n\nexport function fromNow(date: Date): string {\n return fromNowDetailed(date).result;\n}\n\nundefined?.test(\"fromNow\", ({ expect }) => {\n // Set a fixed date for testing\n const fixedDate = new Date(\"2023-01-15T12:00:00.000Z\");\n\n // Use Vitest's fake timers\n undefined?.vi.useFakeTimers();\n undefined?.vi.setSystemTime(fixedDate);\n\n // Test past times\n expect(fromNow(new Date(\"2023-01-15T11:59:50.000Z\"))).toBe(\"just now\");\n expect(fromNow(new Date(\"2023-01-15T11:59:00.000Z\"))).toBe(\"1 minute ago\");\n expect(fromNow(new Date(\"2023-01-15T11:00:00.000Z\"))).toBe(\"1 hour ago\");\n expect(fromNow(new Date(\"2023-01-14T12:00:00.000Z\"))).toBe(\"1 day ago\");\n expect(fromNow(new Date(\"2023-01-08T12:00:00.000Z\"))).toBe(\"1 week ago\");\n\n // Test future times\n expect(fromNow(new Date(\"2023-01-15T12:00:10.000Z\"))).toBe(\"just now\");\n expect(fromNow(new Date(\"2023-01-15T12:01:00.000Z\"))).toBe(\"in 1 minute\");\n expect(fromNow(new Date(\"2023-01-15T13:00:00.000Z\"))).toBe(\"in 1 hour\");\n expect(fromNow(new Date(\"2023-01-16T12:00:00.000Z\"))).toBe(\"in 1 day\");\n expect(fromNow(new Date(\"2023-01-22T12:00:00.000Z\"))).toBe(\"in 1 week\");\n\n // Test very old dates (should use date format)\n expect(fromNow(new Date(\"2022-01-15T12:00:00.000Z\"))).toMatch(/Jan 15, 2022/);\n\n // Restore real timers\n undefined?.vi.useRealTimers();\n});\n\nexport function fromNowDetailed(date: Date): {\n result: string,\n /**\n * May be Infinity if the result will never change.\n */\n secondsUntilChange: number,\n} {\n if (!(date instanceof Date)) {\n throw new Error(`fromNow only accepts Date objects (received: ${date})`);\n }\n\n const now = new Date();\n const elapsed = now.getTime() - date.getTime();\n\n let remainingInUnit = Math.abs(elapsed) / 1000;\n if (remainingInUnit < 15) {\n return {\n result: 'just now',\n secondsUntilChange: 15 - remainingInUnit,\n };\n }\n let unitInSeconds = 1;\n for (const [nextUnitSize, unitName] of agoUnits) {\n const rounded = Math.round(remainingInUnit);\n if (rounded < nextUnitSize) {\n if (elapsed < 0) {\n return {\n result: `in ${rounded} ${unitName}${rounded === 1 ? '' : 's'}`,\n secondsUntilChange: remainder((remainingInUnit - rounded + 0.5) * unitInSeconds, unitInSeconds),\n };\n } else {\n return {\n result: `${rounded} ${unitName}${rounded === 1 ? '' : 's'} ago`,\n secondsUntilChange: remainder((rounded - remainingInUnit - 0.5) * unitInSeconds, unitInSeconds),\n };\n }\n }\n unitInSeconds *= nextUnitSize;\n remainingInUnit /= nextUnitSize;\n }\n\n return {\n result: date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }),\n secondsUntilChange: Infinity,\n };\n}\n\n/**\n * Returns a string representation of the given date in the format expected by the `datetime-local` input type.\n */\nexport function getInputDatetimeLocalString(date: Date): string {\n date = new Date(date);\n date.setMinutes(date.getMinutes() - date.getTimezoneOffset());\n return date.toISOString().slice(0, 19);\n}\n\nundefined?.test(\"getInputDatetimeLocalString\", ({ expect }) => {\n // Use Vitest's fake timers to ensure consistent timezone behavior\n undefined?.vi.useFakeTimers();\n\n // Test with a specific date\n const mockDate = new Date(\"2023-01-15T12:30:45.000Z\");\n const result = getInputDatetimeLocalString(mockDate);\n\n // The result should be in the format YYYY-MM-DDTHH:MM:SS\n expect(result).toMatch(/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$/);\n\n // Test with different dates\n const dates = [\n new Date(\"2023-01-01T00:00:00.000Z\"),\n new Date(\"2023-06-15T23:59:59.000Z\"),\n new Date(\"2023-12-31T12:34:56.000Z\"),\n ];\n\n for (const date of dates) {\n const result = getInputDatetimeLocalString(date);\n expect(result).toMatch(/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$/);\n }\n\n // Restore real timers\n undefined?.vi.useRealTimers();\n});\n\n\nexport type Interval = [number, 'millisecond' | 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'year'];\nexport type DayInterval = [number, 'day' | 'week' | 'month' | 'year'];\n\nfunction applyInterval(date: Date, times: number, interval: Interval): Date {\n if (!intervalSchema.isValidSync(interval)) {\n throw new StackAssertionError(`Invalid interval`, { interval });\n }\n const [amount, unit] = interval;\n switch (unit) {\n case 'millisecond': {\n date.setMilliseconds(date.getMilliseconds() + amount * times);\n break;\n }\n case 'second': {\n date.setSeconds(date.getSeconds() + amount * times);\n break;\n }\n case 'minute': {\n date.setMinutes(date.getMinutes() + amount * times);\n break;\n }\n case 'hour': {\n date.setHours(date.getHours() + amount * times);\n break;\n }\n case 'day': {\n date.setDate(date.getDate() + amount * times);\n break;\n }\n case 'week': {\n date.setDate(date.getDate() + amount * times * 7);\n break;\n }\n case 'month': {\n date.setMonth(date.getMonth() + amount * times);\n break;\n }\n case 'year': {\n date.setFullYear(date.getFullYear() + amount * times);\n break;\n }\n default: {\n throw new StackAssertionError(`Invalid interval despite schema validation`, { interval });\n }\n }\n return date;\n}\n\nexport function subtractInterval(date: Date, interval: Interval): Date {\n return applyInterval(date, -1, interval);\n}\n\nexport function addInterval(date: Date, interval: Interval): Date {\n return applyInterval(date, 1, interval);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/utils/dates.tsx"],"sourcesContent":["import { intervalSchema } from \"../schema-fields\";\nimport { StackAssertionError } from \"./errors\";\nimport { remainder } from \"./math\";\n\nexport function isWeekend(date: Date): boolean {\n return date.getDay() === 0 || date.getDay() === 6;\n}\n\nundefined?.test(\"isWeekend\", ({ expect }) => {\n // Sunday (day 0)\n expect(isWeekend(new Date(2023, 0, 1))).toBe(true);\n // Saturday (day 6)\n expect(isWeekend(new Date(2023, 0, 7))).toBe(true);\n // Monday (day 1)\n expect(isWeekend(new Date(2023, 0, 2))).toBe(false);\n // Friday (day 5)\n expect(isWeekend(new Date(2023, 0, 6))).toBe(false);\n});\n\nconst agoUnits = [\n [60, 'second'],\n [60, 'minute'],\n [24, 'hour'],\n [7, 'day'],\n [5, 'week'],\n] as const;\n\nexport function fromNow(date: Date): string {\n return fromNowDetailed(date).result;\n}\n\nundefined?.test(\"fromNow\", ({ expect }) => {\n // Set a fixed date for testing\n const fixedDate = new Date(\"2023-01-15T12:00:00.000Z\");\n\n // Use Vitest's fake timers\n undefined?.vi.useFakeTimers();\n undefined?.vi.setSystemTime(fixedDate);\n\n // Test past times\n expect(fromNow(new Date(\"2023-01-15T11:59:50.000Z\"))).toBe(\"just now\");\n expect(fromNow(new Date(\"2023-01-15T11:59:00.000Z\"))).toBe(\"1 minute ago\");\n expect(fromNow(new Date(\"2023-01-15T11:00:00.000Z\"))).toBe(\"1 hour ago\");\n expect(fromNow(new Date(\"2023-01-14T12:00:00.000Z\"))).toBe(\"1 day ago\");\n expect(fromNow(new Date(\"2023-01-08T12:00:00.000Z\"))).toBe(\"1 week ago\");\n\n // Test future times\n expect(fromNow(new Date(\"2023-01-15T12:00:10.000Z\"))).toBe(\"just now\");\n expect(fromNow(new Date(\"2023-01-15T12:01:00.000Z\"))).toBe(\"in 1 minute\");\n expect(fromNow(new Date(\"2023-01-15T13:00:00.000Z\"))).toBe(\"in 1 hour\");\n expect(fromNow(new Date(\"2023-01-16T12:00:00.000Z\"))).toBe(\"in 1 day\");\n expect(fromNow(new Date(\"2023-01-22T12:00:00.000Z\"))).toBe(\"in 1 week\");\n\n // Test very old dates (should use date format)\n expect(fromNow(new Date(\"2022-01-15T12:00:00.000Z\"))).toMatch(/Jan 15, 2022/);\n\n // Restore real timers\n undefined?.vi.useRealTimers();\n});\n\nexport function fromNowDetailed(date: Date): {\n result: string,\n /**\n * May be Infinity if the result will never change.\n */\n secondsUntilChange: number,\n} {\n if (!(date instanceof Date)) {\n throw new Error(`fromNow only accepts Date objects (received: ${date})`);\n }\n\n const now = new Date();\n const elapsed = now.getTime() - date.getTime();\n\n let remainingInUnit = Math.abs(elapsed) / 1000;\n if (remainingInUnit < 15) {\n return {\n result: 'just now',\n secondsUntilChange: 15 - remainingInUnit,\n };\n }\n let unitInSeconds = 1;\n for (const [nextUnitSize, unitName] of agoUnits) {\n const rounded = Math.round(remainingInUnit);\n if (rounded < nextUnitSize) {\n if (elapsed < 0) {\n return {\n result: `in ${rounded} ${unitName}${rounded === 1 ? '' : 's'}`,\n secondsUntilChange: remainder((remainingInUnit - rounded + 0.5) * unitInSeconds, unitInSeconds),\n };\n } else {\n return {\n result: `${rounded} ${unitName}${rounded === 1 ? '' : 's'} ago`,\n secondsUntilChange: remainder((rounded - remainingInUnit - 0.5) * unitInSeconds, unitInSeconds),\n };\n }\n }\n unitInSeconds *= nextUnitSize;\n remainingInUnit /= nextUnitSize;\n }\n\n return {\n result: date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }),\n secondsUntilChange: Infinity,\n };\n}\n\n/**\n * Returns a string representation of the given date in the format expected by the `datetime-local` input type.\n */\nexport function getInputDatetimeLocalString(date: Date): string {\n date = new Date(date);\n date.setMinutes(date.getMinutes() - date.getTimezoneOffset());\n return date.toISOString().slice(0, 19);\n}\n\nundefined?.test(\"getInputDatetimeLocalString\", ({ expect }) => {\n // Use Vitest's fake timers to ensure consistent timezone behavior\n undefined?.vi.useFakeTimers();\n\n // Test with a specific date\n const mockDate = new Date(\"2023-01-15T12:30:45.000Z\");\n const result = getInputDatetimeLocalString(mockDate);\n\n // The result should be in the format YYYY-MM-DDTHH:MM:SS\n expect(result).toMatch(/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$/);\n\n // Test with different dates\n const dates = [\n new Date(\"2023-01-01T00:00:00.000Z\"),\n new Date(\"2023-06-15T23:59:59.000Z\"),\n new Date(\"2023-12-31T12:34:56.000Z\"),\n ];\n\n for (const date of dates) {\n const result = getInputDatetimeLocalString(date);\n expect(result).toMatch(/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$/);\n }\n\n // Restore real timers\n undefined?.vi.useRealTimers();\n});\n\n\nexport type Interval = [number, 'millisecond' | 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'year'];\nexport type DayInterval = [number, 'day' | 'week' | 'month' | 'year'];\n\nfunction applyInterval(date: Date, times: number, interval: Interval): Date {\n if (!intervalSchema.isValidSync(interval)) {\n throw new StackAssertionError(`Invalid interval`, { interval });\n }\n const [amount, unit] = interval;\n switch (unit) {\n case 'millisecond': {\n date.setMilliseconds(date.getMilliseconds() + amount * times);\n break;\n }\n case 'second': {\n date.setSeconds(date.getSeconds() + amount * times);\n break;\n }\n case 'minute': {\n date.setMinutes(date.getMinutes() + amount * times);\n break;\n }\n case 'hour': {\n date.setHours(date.getHours() + amount * times);\n break;\n }\n case 'day': {\n date.setDate(date.getDate() + amount * times);\n break;\n }\n case 'week': {\n date.setDate(date.getDate() + amount * times * 7);\n break;\n }\n case 'month': {\n date.setMonth(date.getMonth() + amount * times);\n break;\n }\n case 'year': {\n date.setFullYear(date.getFullYear() + amount * times);\n break;\n }\n default: {\n throw new StackAssertionError(`Invalid interval despite schema validation`, { interval });\n }\n }\n return date;\n}\n\nexport function subtractInterval(date: Date, interval: Interval): Date {\n return applyInterval(date, -1, interval);\n}\n\nexport function addInterval(date: Date, interval: Interval): Date {\n return applyInterval(date, 1, interval);\n}\n\nexport const FAR_FUTURE_DATE = new Date(8640000000000000); // 13 Sep 275760 00:00:00 UTC\n\nfunction getMsPerDayIntervalUnit(unit: 'day' | 'week'): number {\n if (unit === 'day') {\n return 24 * 60 * 60 * 1000;\n }\n return 7 * 24 * 60 * 60 * 1000;\n}\n\n\nexport function getIntervalsElapsed(anchor: Date, to: Date, repeat: DayInterval): number {\n const [amount, unit] = repeat;\n if (to <= anchor) return 0;\n if (unit === 'day' || unit === 'week') {\n const msPerUnit = getMsPerDayIntervalUnit(unit);\n const diffMs = to.getTime() - anchor.getTime();\n return Math.floor(diffMs / (msPerUnit * amount));\n }\n if ([\"month\", \"year\"].includes(unit)) {\n let count = 0;\n let current = new Date(anchor);\n for (; ;) {\n const next = addInterval(new Date(current), [amount, unit]);\n if (next > to) break;\n current = next;\n count += 1;\n }\n return count;\n }\n return 0;\n}\n\nundefined?.test(\"getIntervalsElapsed\", ({ expect }) => {\n const anchor = new Date('2025-01-01T00:00:00.000Z');\n const to = new Date('2025-01-15T00:00:00.000Z');\n expect(getIntervalsElapsed(anchor, to, [1, 'week'])).toBe(2);\n expect(getIntervalsElapsed(anchor, to, [3, 'day'])).toBe(4);\n\n const mAnchor = new Date('2023-01-31T00:00:00.000Z');\n const mTo = new Date('2023-03-01T00:00:00.000Z');\n expect(getIntervalsElapsed(mAnchor, mTo, [1, 'month'])).toBe(0);\n\n const yAnchor = new Date('2020-01-01T00:00:00.000Z');\n const yTo = new Date('2022-06-01T00:00:00.000Z');\n expect(getIntervalsElapsed(yAnchor, yTo, [1, 'year'])).toBe(2);\n});\n"],"mappings":"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","names":[]}
@@ -1,7 +1,5 @@
1
1
  import * as jose from 'jose';
2
2
 
3
- declare function legacySignGlobalJWT(issuer: string, payload: any, expirationTime?: string): Promise<string>;
4
- declare function legacyVerifyGlobalJWT(issuer: string, jwt: string): Promise<jose.JWTPayload>;
5
3
  declare function signJWT(options: {
6
4
  issuer: string;
7
5
  audience: string;
@@ -9,7 +7,7 @@ declare function signJWT(options: {
9
7
  expirationTime?: string;
10
8
  }): Promise<string>;
11
9
  declare function verifyJWT(options: {
12
- issuer: string;
10
+ allowedIssuers: string[];
13
11
  jwt: string;
14
12
  }): Promise<jose.JWTPayload>;
15
13
  type PrivateJwk = {
@@ -21,7 +19,13 @@ type PrivateJwk = {
21
19
  x: string;
22
20
  y: string;
23
21
  };
24
- declare function getPrivateJwk(secret: string): Promise<PrivateJwk>;
22
+ /**
23
+ * Returns a list of valid private JWKs for the given audience, with the first one taking precedence when signing new
24
+ * JWTs.
25
+ */
26
+ declare function getPrivateJwks(options: {
27
+ audience: string;
28
+ }): Promise<PrivateJwk[]>;
25
29
  type PublicJwk = {
26
30
  kty: "EC";
27
31
  alg: "ES256";
@@ -30,15 +34,11 @@ type PublicJwk = {
30
34
  x: string;
31
35
  y: string;
32
36
  };
33
- declare function getPublicJwkSet(secretOrPrivateJwk: string | PrivateJwk): Promise<{
37
+ declare function getPublicJwkSet(privateJwks: PrivateJwk[]): Promise<{
34
38
  keys: PublicJwk[];
35
39
  }>;
36
- declare function getPerAudienceSecret(options: {
37
- audience: string;
38
- secret: string;
39
- }): string;
40
- declare function getKid(options: {
40
+ declare function oldGetKid(options: {
41
41
  secret: string;
42
42
  }): string;
43
43
 
44
- export { type PrivateJwk, type PublicJwk, getKid, getPerAudienceSecret, getPrivateJwk, getPublicJwkSet, legacySignGlobalJWT, legacyVerifyGlobalJWT, signJWT, verifyJWT };
44
+ export { type PrivateJwk, type PublicJwk, getPrivateJwks, getPublicJwkSet, oldGetKid, signJWT, verifyJWT };
@@ -1,7 +1,5 @@
1
1
  import * as jose from 'jose';
2
2
 
3
- declare function legacySignGlobalJWT(issuer: string, payload: any, expirationTime?: string): Promise<string>;
4
- declare function legacyVerifyGlobalJWT(issuer: string, jwt: string): Promise<jose.JWTPayload>;
5
3
  declare function signJWT(options: {
6
4
  issuer: string;
7
5
  audience: string;
@@ -9,7 +7,7 @@ declare function signJWT(options: {
9
7
  expirationTime?: string;
10
8
  }): Promise<string>;
11
9
  declare function verifyJWT(options: {
12
- issuer: string;
10
+ allowedIssuers: string[];
13
11
  jwt: string;
14
12
  }): Promise<jose.JWTPayload>;
15
13
  type PrivateJwk = {
@@ -21,7 +19,13 @@ type PrivateJwk = {
21
19
  x: string;
22
20
  y: string;
23
21
  };
24
- declare function getPrivateJwk(secret: string): Promise<PrivateJwk>;
22
+ /**
23
+ * Returns a list of valid private JWKs for the given audience, with the first one taking precedence when signing new
24
+ * JWTs.
25
+ */
26
+ declare function getPrivateJwks(options: {
27
+ audience: string;
28
+ }): Promise<PrivateJwk[]>;
25
29
  type PublicJwk = {
26
30
  kty: "EC";
27
31
  alg: "ES256";
@@ -30,15 +34,11 @@ type PublicJwk = {
30
34
  x: string;
31
35
  y: string;
32
36
  };
33
- declare function getPublicJwkSet(secretOrPrivateJwk: string | PrivateJwk): Promise<{
37
+ declare function getPublicJwkSet(privateJwks: PrivateJwk[]): Promise<{
34
38
  keys: PublicJwk[];
35
39
  }>;
36
- declare function getPerAudienceSecret(options: {
37
- audience: string;
38
- secret: string;
39
- }): string;
40
- declare function getKid(options: {
40
+ declare function oldGetKid(options: {
41
41
  secret: string;
42
42
  }): string;
43
43
 
44
- export { type PrivateJwk, type PublicJwk, getKid, getPerAudienceSecret, getPrivateJwk, getPublicJwkSet, legacySignGlobalJWT, legacyVerifyGlobalJWT, signJWT, verifyJWT };
44
+ export { type PrivateJwk, type PublicJwk, getPrivateJwks, getPublicJwkSet, oldGetKid, signJWT, verifyJWT };
package/dist/utils/jwt.js CHANGED
@@ -30,12 +30,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/utils/jwt.tsx
31
31
  var jwt_exports = {};
32
32
  __export(jwt_exports, {
33
- getKid: () => getKid,
34
- getPerAudienceSecret: () => getPerAudienceSecret,
35
- getPrivateJwk: () => getPrivateJwk,
33
+ getPrivateJwks: () => getPrivateJwks,
36
34
  getPublicJwkSet: () => getPublicJwkSet,
37
- legacySignGlobalJWT: () => legacySignGlobalJWT,
38
- legacyVerifyGlobalJWT: () => legacyVerifyGlobalJWT,
35
+ oldGetKid: () => oldGetKid,
39
36
  signJWT: () => signJWT,
40
37
  verifyJWT: () => verifyJWT
41
38
  });
@@ -45,42 +42,36 @@ var import_elliptic = __toESM(require("elliptic"));
45
42
  var jose = __toESM(require("jose"));
46
43
  var import_errors = require("jose/errors");
47
44
  var import_bytes = require("./bytes.js");
45
+ var import_env = require("./env.js");
48
46
  var import_errors2 = require("./errors.js");
49
47
  var import_globals = require("./globals.js");
50
48
  var import_objects = require("./objects.js");
51
- var STACK_SERVER_SECRET = process.env.STACK_SERVER_SECRET ?? "";
52
- try {
53
- jose.base64url.decode(STACK_SERVER_SECRET);
54
- } catch (e) {
55
- throw new Error("STACK_SERVER_SECRET is not valid. Please use the generateKeys script to generate a new secret.");
56
- }
57
- async function legacySignGlobalJWT(issuer, payload, expirationTime = "5m") {
58
- const privateJwk = await jose.importJWK(await getPrivateJwk(STACK_SERVER_SECRET));
59
- return await new jose.SignJWT(payload).setProtectedHeader({ alg: "ES256" }).setIssuer(issuer).setIssuedAt().setExpirationTime(expirationTime).sign(privateJwk);
60
- }
61
- async function legacyVerifyGlobalJWT(issuer, jwt) {
62
- const jwkSet = jose.createLocalJWKSet(await getPublicJwkSet(STACK_SERVER_SECRET));
63
- const verified = await jose.jwtVerify(jwt, jwkSet, { issuer });
64
- return verified.payload;
49
+ function getStackServerSecret() {
50
+ const STACK_SERVER_SECRET = (0, import_env.getEnvVariable)("STACK_SERVER_SECRET");
51
+ try {
52
+ jose.base64url.decode(STACK_SERVER_SECRET);
53
+ } catch (e) {
54
+ throw new import_errors2.StackAssertionError("STACK_SERVER_SECRET is not valid. Please use the generateKeys script to generate a new secret.", { cause: e });
55
+ }
56
+ return STACK_SERVER_SECRET;
65
57
  }
66
58
  async function signJWT(options) {
67
- const secret = getPerAudienceSecret({ audience: options.audience, secret: STACK_SERVER_SECRET });
68
- const kid = getKid({ secret });
69
- const privateJwk = await jose.importJWK(await getPrivateJwk(secret));
70
- return await new jose.SignJWT(options.payload).setProtectedHeader({ alg: "ES256", kid }).setIssuer(options.issuer).setIssuedAt().setAudience(options.audience).setExpirationTime(options.expirationTime || "5m").sign(privateJwk);
59
+ const privateJwks = await getPrivateJwks({ audience: options.audience });
60
+ const privateKey = await jose.importJWK(privateJwks[0]);
61
+ return await new jose.SignJWT(options.payload).setProtectedHeader({ alg: "ES256", kid: privateJwks[0].kid }).setIssuer(options.issuer).setIssuedAt().setAudience(options.audience).setExpirationTime(options.expirationTime || "5m").sign(privateKey);
71
62
  }
72
63
  async function verifyJWT(options) {
73
- const audience = jose.decodeJwt(options.jwt).aud;
64
+ const decodedJwt = jose.decodeJwt(options.jwt);
65
+ const audience = decodedJwt.aud;
74
66
  if (!audience || typeof audience !== "string") {
75
67
  throw new import_errors.JOSEError("Invalid JWT audience");
76
68
  }
77
- const secret = getPerAudienceSecret({ audience, secret: STACK_SERVER_SECRET });
78
- const jwkSet = jose.createLocalJWKSet(await getPublicJwkSet(secret));
79
- const verified = await jose.jwtVerify(options.jwt, jwkSet, { issuer: options.issuer });
69
+ const jwkSet = jose.createLocalJWKSet(await getPublicJwkSet(await getPrivateJwks({ audience })));
70
+ const verified = await jose.jwtVerify(options.jwt, jwkSet, { issuer: options.allowedIssuers });
80
71
  return verified.payload;
81
72
  }
82
- async function getPrivateJwk(secret) {
83
- const secretHash = await import_globals.globalVar.crypto.subtle.digest("SHA-256", jose.base64url.decode(secret));
73
+ async function getPrivateJwkFromDerivedSecret(derivedSecret, kid) {
74
+ const secretHash = await import_globals.globalVar.crypto.subtle.digest("SHA-256", jose.base64url.decode(derivedSecret));
84
75
  const priv = new Uint8Array(secretHash);
85
76
  const ec = new import_elliptic.default.ec("p256");
86
77
  const key = ec.keyFromPrivate(priv);
@@ -89,40 +80,51 @@ async function getPrivateJwk(secret) {
89
80
  kty: "EC",
90
81
  crv: "P-256",
91
82
  alg: "ES256",
92
- kid: getKid({ secret }),
83
+ kid,
93
84
  d: (0, import_bytes.encodeBase64Url)(priv),
94
85
  x: (0, import_bytes.encodeBase64Url)(publicKey.getX().toBuffer()),
95
86
  y: (0, import_bytes.encodeBase64Url)(publicKey.getY().toBuffer())
96
87
  };
97
88
  }
98
- async function getPublicJwkSet(secretOrPrivateJwk) {
99
- const privateJwk = typeof secretOrPrivateJwk === "string" ? await getPrivateJwk(secretOrPrivateJwk) : secretOrPrivateJwk;
100
- const jwk = (0, import_objects.pick)(privateJwk, ["kty", "alg", "crv", "x", "y", "kid"]);
89
+ async function getPrivateJwks(options) {
90
+ const getHashOfJwkInfo = (type) => jose.base64url.encode(
91
+ import_crypto.default.createHash("sha256").update(JSON.stringify([type, getStackServerSecret(), {
92
+ audience: options.audience
93
+ }])).digest()
94
+ );
95
+ const perAudienceSecret = getHashOfJwkInfo("stack-jwk-audience-secret");
96
+ const perAudienceKid = getHashOfJwkInfo("stack-jwk-kid").slice(0, 12);
97
+ const oldPerAudienceSecret = oldGetPerAudienceSecret({ audience: options.audience });
98
+ const oldPerAudienceKid = oldGetKid({ secret: oldPerAudienceSecret });
99
+ return [
100
+ // TODO next-release: make this not take precedence; then, in the release after that, remove it entirely
101
+ await getPrivateJwkFromDerivedSecret(oldPerAudienceSecret, oldPerAudienceKid),
102
+ await getPrivateJwkFromDerivedSecret(perAudienceSecret, perAudienceKid)
103
+ ];
104
+ }
105
+ async function getPublicJwkSet(privateJwks) {
101
106
  return {
102
- keys: [jwk]
107
+ keys: privateJwks.map((jwk) => (0, import_objects.pick)(jwk, ["kty", "alg", "crv", "x", "y", "kid"]))
103
108
  };
104
109
  }
105
- function getPerAudienceSecret(options) {
110
+ function oldGetPerAudienceSecret(options) {
106
111
  if (options.audience === "kid") {
107
112
  throw new import_errors2.StackAssertionError("You cannot use the 'kid' audience for a per-audience secret, see comment below in jwt.tsx");
108
113
  }
109
114
  return jose.base64url.encode(
110
- import_crypto.default.createHash("sha256").update(JSON.stringify([options.secret, options.audience])).digest()
115
+ import_crypto.default.createHash("sha256").update(JSON.stringify([getStackServerSecret(), options.audience])).digest()
111
116
  );
112
117
  }
113
- function getKid(options) {
118
+ function oldGetKid(options) {
114
119
  return jose.base64url.encode(
115
120
  import_crypto.default.createHash("sha256").update(JSON.stringify([options.secret, "kid"])).digest()
116
121
  ).slice(0, 12);
117
122
  }
118
123
  // Annotate the CommonJS export names for ESM import in node:
119
124
  0 && (module.exports = {
120
- getKid,
121
- getPerAudienceSecret,
122
- getPrivateJwk,
125
+ getPrivateJwks,
123
126
  getPublicJwkSet,
124
- legacySignGlobalJWT,
125
- legacyVerifyGlobalJWT,
127
+ oldGetKid,
126
128
  signJWT,
127
129
  verifyJWT
128
130
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/jwt.tsx"],"sourcesContent":["import crypto from \"crypto\";\nimport elliptic from \"elliptic\";\nimport * as jose from \"jose\";\nimport { JOSEError } from \"jose/errors\";\nimport { encodeBase64Url } from \"./bytes\";\nimport { StackAssertionError } from \"./errors\";\nimport { globalVar } from \"./globals\";\nimport { pick } from \"./objects\";\n\nconst STACK_SERVER_SECRET = process.env.STACK_SERVER_SECRET ?? \"\";\ntry {\n jose.base64url.decode(STACK_SERVER_SECRET);\n} catch (e) {\n throw new Error(\"STACK_SERVER_SECRET is not valid. Please use the generateKeys script to generate a new secret.\");\n}\n\n// TODO: remove this after moving everyone to project specific JWTs\nexport async function legacySignGlobalJWT(issuer: string, payload: any, expirationTime = \"5m\") {\n const privateJwk = await jose.importJWK(await getPrivateJwk(STACK_SERVER_SECRET));\n return await new jose.SignJWT(payload)\n .setProtectedHeader({ alg: \"ES256\" })\n .setIssuer(issuer)\n .setIssuedAt()\n .setExpirationTime(expirationTime)\n .sign(privateJwk);\n}\n\n// TODO: remove this after moving everyone to project specific JWTs\nexport async function legacyVerifyGlobalJWT(issuer: string, jwt: string) {\n const jwkSet = jose.createLocalJWKSet(await getPublicJwkSet(STACK_SERVER_SECRET));\n const verified = await jose.jwtVerify(jwt, jwkSet, { issuer });\n return verified.payload;\n}\n\nexport async function signJWT(options: {\n issuer: string,\n audience: string,\n payload: any,\n expirationTime?: string,\n}) {\n const secret = getPerAudienceSecret({ audience: options.audience, secret: STACK_SERVER_SECRET });\n const kid = getKid({ secret });\n const privateJwk = await jose.importJWK(await getPrivateJwk(secret));\n return await new jose.SignJWT(options.payload)\n .setProtectedHeader({ alg: \"ES256\", kid })\n .setIssuer(options.issuer)\n .setIssuedAt()\n .setAudience(options.audience)\n .setExpirationTime(options.expirationTime || \"5m\")\n .sign(privateJwk);\n}\n\nexport async function verifyJWT(options: {\n issuer: string,\n jwt: string,\n}) {\n const audience = jose.decodeJwt(options.jwt).aud;\n if (!audience || typeof audience !== \"string\") {\n throw new JOSEError(\"Invalid JWT audience\");\n }\n const secret = getPerAudienceSecret({ audience, secret: STACK_SERVER_SECRET });\n const jwkSet = jose.createLocalJWKSet(await getPublicJwkSet(secret));\n const verified = await jose.jwtVerify(options.jwt, jwkSet, { issuer: options.issuer });\n return verified.payload;\n}\n\nexport type PrivateJwk = {\n kty: \"EC\",\n alg: \"ES256\",\n crv: \"P-256\",\n kid: string,\n d: string,\n x: string,\n y: string,\n};\nexport async function getPrivateJwk(secret: string): Promise<PrivateJwk> {\n const secretHash = await globalVar.crypto.subtle.digest(\"SHA-256\", jose.base64url.decode(secret));\n const priv = new Uint8Array(secretHash);\n\n const ec = new elliptic.ec('p256');\n const key = ec.keyFromPrivate(priv);\n const publicKey = key.getPublic();\n\n return {\n kty: 'EC',\n crv: 'P-256',\n alg: 'ES256',\n kid: getKid({ secret }),\n d: encodeBase64Url(priv),\n x: encodeBase64Url(publicKey.getX().toBuffer()),\n y: encodeBase64Url(publicKey.getY().toBuffer()),\n };\n}\n\nexport type PublicJwk = {\n kty: \"EC\",\n alg: \"ES256\",\n crv: \"P-256\",\n kid: string,\n x: string,\n y: string,\n};\nexport async function getPublicJwkSet(secretOrPrivateJwk: string | PrivateJwk): Promise<{ keys: PublicJwk[] }> {\n const privateJwk = typeof secretOrPrivateJwk === \"string\" ? await getPrivateJwk(secretOrPrivateJwk) : secretOrPrivateJwk;\n const jwk = pick(privateJwk, [\"kty\", \"alg\", \"crv\", \"x\", \"y\", \"kid\"]);\n return {\n keys: [jwk],\n };\n}\n\nexport function getPerAudienceSecret(options: {\n audience: string,\n secret: string,\n}) {\n if (options.audience === \"kid\") {\n throw new StackAssertionError(\"You cannot use the 'kid' audience for a per-audience secret, see comment below in jwt.tsx\");\n }\n return jose.base64url.encode(\n crypto\n .createHash('sha256')\n // TODO we should prefix a string like \"stack-audience-secret\" before we hash so you can't use `getKid(...)` to get the secret for eg. the \"kid\" audience if the same secret value is used\n // Sadly doing this modification is a bit annoying as we need to leave the old keys to be valid for a little longer\n .update(JSON.stringify([options.secret, options.audience]))\n .digest()\n );\n};\n\nexport function getKid(options: {\n secret: string,\n}) {\n return jose.base64url.encode(\n crypto\n .createHash('sha256')\n .update(JSON.stringify([options.secret, \"kid\"])) // TODO see above in getPerAudienceSecret\n .digest()\n ).slice(0, 12);\n}\n"],"mappings":"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","names":["import_errors","elliptic","crypto","crypto"]}
1
+ {"version":3,"sources":["../../src/utils/jwt.tsx"],"sourcesContent":["import crypto from \"crypto\";\nimport elliptic from \"elliptic\";\nimport * as jose from \"jose\";\nimport { JOSEError } from \"jose/errors\";\nimport { encodeBase64Url } from \"./bytes\";\nimport { getEnvVariable } from \"./env\";\nimport { StackAssertionError } from \"./errors\";\nimport { globalVar } from \"./globals\";\nimport { pick } from \"./objects\";\n\nfunction getStackServerSecret() {\n const STACK_SERVER_SECRET = getEnvVariable(\"STACK_SERVER_SECRET\");\n try {\n jose.base64url.decode(STACK_SERVER_SECRET);\n } catch (e) {\n throw new StackAssertionError(\"STACK_SERVER_SECRET is not valid. Please use the generateKeys script to generate a new secret.\", { cause: e });\n }\n return STACK_SERVER_SECRET;\n}\n\nexport async function signJWT(options: {\n issuer: string,\n audience: string,\n payload: any,\n expirationTime?: string,\n}) {\n const privateJwks = await getPrivateJwks({ audience: options.audience });\n const privateKey = await jose.importJWK(privateJwks[0]);\n\n return await new jose.SignJWT(options.payload)\n .setProtectedHeader({ alg: \"ES256\", kid: privateJwks[0].kid })\n .setIssuer(options.issuer)\n .setIssuedAt()\n .setAudience(options.audience)\n .setExpirationTime(options.expirationTime || \"5m\")\n .sign(privateKey);\n}\n\nexport async function verifyJWT(options: {\n allowedIssuers: string[],\n jwt: string,\n}) {\n const decodedJwt = jose.decodeJwt(options.jwt);\n const audience = decodedJwt.aud;\n if (!audience || typeof audience !== \"string\") {\n throw new JOSEError(\"Invalid JWT audience\");\n }\n\n const jwkSet = jose.createLocalJWKSet(await getPublicJwkSet(await getPrivateJwks({ audience })));\n const verified = await jose.jwtVerify(options.jwt, jwkSet, { issuer: options.allowedIssuers });\n return verified.payload;\n}\n\nexport type PrivateJwk = {\n kty: \"EC\",\n alg: \"ES256\",\n crv: \"P-256\",\n kid: string,\n d: string,\n x: string,\n y: string,\n};\nasync function getPrivateJwkFromDerivedSecret(derivedSecret: string, kid: string): Promise<PrivateJwk> {\n const secretHash = await globalVar.crypto.subtle.digest(\"SHA-256\", jose.base64url.decode(derivedSecret));\n const priv = new Uint8Array(secretHash);\n\n const ec = new elliptic.ec('p256');\n const key = ec.keyFromPrivate(priv);\n const publicKey = key.getPublic();\n\n return {\n kty: 'EC',\n crv: 'P-256',\n alg: 'ES256',\n kid: kid,\n d: encodeBase64Url(priv),\n x: encodeBase64Url(publicKey.getX().toBuffer()),\n y: encodeBase64Url(publicKey.getY().toBuffer()),\n };\n}\n\n/**\n * Returns a list of valid private JWKs for the given audience, with the first one taking precedence when signing new\n * JWTs.\n */\nexport async function getPrivateJwks(options: {\n audience: string,\n}): Promise<PrivateJwk[]> {\n const getHashOfJwkInfo = (type: string) => jose.base64url.encode(\n crypto\n .createHash('sha256')\n .update(JSON.stringify([type, getStackServerSecret(), {\n audience: options.audience,\n }]))\n .digest()\n );\n const perAudienceSecret = getHashOfJwkInfo(\"stack-jwk-audience-secret\");\n const perAudienceKid = getHashOfJwkInfo(\"stack-jwk-kid\").slice(0, 12);\n\n const oldPerAudienceSecret = oldGetPerAudienceSecret({ audience: options.audience });\n const oldPerAudienceKid = oldGetKid({ secret: oldPerAudienceSecret });\n\n return [\n // TODO next-release: make this not take precedence; then, in the release after that, remove it entirely\n await getPrivateJwkFromDerivedSecret(oldPerAudienceSecret, oldPerAudienceKid),\n\n await getPrivateJwkFromDerivedSecret(perAudienceSecret, perAudienceKid),\n ];\n}\n\nexport type PublicJwk = {\n kty: \"EC\",\n alg: \"ES256\",\n crv: \"P-256\",\n kid: string,\n x: string,\n y: string,\n};\nexport async function getPublicJwkSet(privateJwks: PrivateJwk[]): Promise<{ keys: PublicJwk[] }> {\n return {\n keys: privateJwks.map(jwk => pick(jwk, [\"kty\", \"alg\", \"crv\", \"x\", \"y\", \"kid\"])),\n };\n}\n\nfunction oldGetPerAudienceSecret(options: {\n audience: string,\n}) {\n if (options.audience === \"kid\") {\n throw new StackAssertionError(\"You cannot use the 'kid' audience for a per-audience secret, see comment below in jwt.tsx\");\n }\n return jose.base64url.encode(\n crypto\n .createHash('sha256')\n // TODO we should prefix a string like \"stack-audience-secret\" before we hash so you can't use `getKid(...)` to get the secret for eg. the \"kid\" audience if the same secret value is used\n // Sadly doing this modification is a bit annoying as we need to leave the old keys to be valid for a little longer\n .update(JSON.stringify([getStackServerSecret(), options.audience]))\n .digest()\n );\n};\n\nexport function oldGetKid(options: {\n secret: string,\n}) {\n return jose.base64url.encode(\n crypto\n .createHash('sha256')\n .update(JSON.stringify([options.secret, \"kid\"])) // TODO see above in getPerAudienceSecret\n .digest()\n ).slice(0, 12);\n}\n"],"mappings":"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","names":["import_errors","elliptic","crypto","crypto"]}
@@ -232,6 +232,9 @@ ${currentIndent}`;
232
232
  if (ArrayBuffer.isView(value)) {
233
233
  return `${value.constructor.name}([${value.toString()}])`;
234
234
  }
235
+ if (value instanceof ArrayBuffer) {
236
+ return `ArrayBuffer [${new Uint8Array(value).toString()}]`;
237
+ }
235
238
  if (value instanceof Error) {
236
239
  let stack = value.stack ?? "";
237
240
  const toString = value.toString();