@fonoster/common 0.26.0 → 0.30.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.
Files changed (176) hide show
  1. package/dist/assistants/index.d.ts +2 -6
  2. package/dist/assistants/index.js +2 -6
  3. package/dist/assistants/sessionAssistantSchema.d.ts +741 -0
  4. package/dist/assistants/sessionAssistantSchema.js +112 -0
  5. package/dist/assistants/testCasesSchema.d.ts +4 -4
  6. package/dist/{voice/Dial.d.ts → calls/index.d.ts} +16 -13
  7. package/dist/calls/index.js +44 -0
  8. package/dist/cellsecrets/index.d.ts +91 -0
  9. package/dist/cellsecrets/index.js +299 -0
  10. package/dist/config/cli/check.d.ts +6 -0
  11. package/dist/config/cli/check.js +202 -0
  12. package/dist/config/cli/envFile.d.ts +23 -0
  13. package/dist/config/cli/envFile.js +81 -0
  14. package/dist/config/cli/exposure.d.ts +10 -0
  15. package/dist/config/cli/exposure.js +93 -0
  16. package/dist/config/cli/index.d.ts +2 -0
  17. package/dist/{identity/tokenHasAccessKeyId.js → config/cli/index.js} +7 -8
  18. package/dist/config/cli/info.d.ts +6 -0
  19. package/dist/config/cli/info.js +62 -0
  20. package/dist/config/cli/init.d.ts +9 -0
  21. package/dist/config/cli/init.js +152 -0
  22. package/dist/config/cli/main.d.ts +11 -0
  23. package/dist/config/cli/main.js +155 -0
  24. package/dist/config/cli/render.d.ts +44 -0
  25. package/dist/config/cli/render.js +129 -0
  26. package/dist/config/cli/secrets.d.ts +21 -0
  27. package/dist/config/cli/secrets.js +117 -0
  28. package/dist/{utils/findIntegrationsCredentials.d.ts → config/cli/types.d.ts} +6 -5
  29. package/dist/config/cli/types.js +7 -0
  30. package/dist/config/configHints.d.ts +39 -0
  31. package/dist/config/configHints.js +61 -0
  32. package/dist/{voice → config}/index.d.ts +12 -13
  33. package/dist/config/index.js +46 -0
  34. package/dist/config/integrationsSchema.d.ts +32 -0
  35. package/dist/config/integrationsSchema.js +56 -0
  36. package/dist/config/interpolateEnv.d.ts +34 -0
  37. package/dist/config/interpolateEnv.js +70 -0
  38. package/dist/config/loadServiceConfig.d.ts +33 -0
  39. package/dist/config/loadServiceConfig.js +121 -0
  40. package/dist/config/logsSchema.d.ts +76 -0
  41. package/dist/config/logsSchema.js +49 -0
  42. package/dist/config/natsConnection.d.ts +53 -0
  43. package/dist/config/natsConnection.js +58 -0
  44. package/dist/config/parseConfigFile.d.ts +7 -0
  45. package/dist/config/parseConfigFile.js +60 -0
  46. package/dist/config/resolveConfigFile.d.ts +17 -0
  47. package/dist/config/resolveConfigFile.js +54 -0
  48. package/dist/config/resolveSecretRefs.d.ts +35 -0
  49. package/dist/config/resolveSecretRefs.js +129 -0
  50. package/dist/{assistants/audioFiltersSchema.d.ts → config/strictDeep.d.ts} +9 -12
  51. package/dist/config/strictDeep.js +58 -0
  52. package/dist/config/telemetrySchema.d.ts +39 -0
  53. package/dist/config/telemetrySchema.js +44 -0
  54. package/dist/config/toJsonSchema.d.ts +30 -0
  55. package/dist/config/toJsonSchema.js +161 -0
  56. package/dist/constants.d.ts +1 -4
  57. package/dist/constants.js +4 -5
  58. package/dist/envs.d.ts +2 -1
  59. package/dist/envs.js +13 -11
  60. package/dist/identity/createAuthInterceptor.d.ts +9 -2
  61. package/dist/identity/createAuthInterceptor.js +36 -30
  62. package/dist/identity/hasAccess.d.ts +9 -3
  63. package/dist/identity/hasAccess.js +32 -8
  64. package/dist/identity/index.d.ts +1 -0
  65. package/dist/identity/index.js +1 -0
  66. package/dist/identity/isValidToken.d.ts +12 -2
  67. package/dist/identity/isValidToken.js +25 -9
  68. package/dist/identity/roles.js +22 -6
  69. package/dist/identity/types.d.ts +6 -1
  70. package/dist/identity/types.js +1 -0
  71. package/dist/{assistants/LanguageModelProvider.js → identity/verifyAccessToken.d.ts} +10 -11
  72. package/dist/identity/verifyAccessToken.js +44 -0
  73. package/dist/index.d.ts +11 -1
  74. package/dist/index.js +14 -2
  75. package/dist/net/assertPublicDestination.d.ts +52 -0
  76. package/dist/net/assertPublicDestination.js +342 -0
  77. package/dist/{voice/PlayDtmf.d.ts → net/index.d.ts} +1 -5
  78. package/dist/{voice → net}/index.js +1 -13
  79. package/dist/notifications/createSendEmail.js +3 -1
  80. package/dist/protos/applications.proto +124 -3
  81. package/dist/protos/authz.proto +23 -4
  82. package/dist/protos/capabilities.proto +94 -0
  83. package/dist/protos/identity.proto +26 -1
  84. package/dist/protos/providers.proto +135 -0
  85. package/dist/protos/webhooks.proto +140 -0
  86. package/dist/{voice/StopSay.d.ts → regions/hub/index.d.ts} +1 -6
  87. package/dist/regions/hub/index.js +35 -0
  88. package/dist/regions/hub/natsJwt.d.ts +63 -0
  89. package/dist/regions/hub/natsJwt.js +195 -0
  90. package/dist/registry/Registry.d.ts +79 -0
  91. package/dist/registry/Registry.js +418 -0
  92. package/dist/registry/cli.d.ts +20 -0
  93. package/dist/registry/cli.js +424 -0
  94. package/dist/{assistants/audioFiltersSchema.js → registry/features.d.ts} +13 -14
  95. package/dist/registry/features.js +36 -0
  96. package/dist/{assistants/LanguageModelProvider.d.ts → registry/index.d.ts} +6 -8
  97. package/dist/registry/index.js +40 -0
  98. package/dist/registry/prisma.d.ts +67 -0
  99. package/dist/registry/prisma.js +391 -0
  100. package/dist/registry/stores.d.ts +21 -0
  101. package/dist/{assistants/eventsHookSchema.js → registry/stores.js} +55 -18
  102. package/dist/registry/types.d.ts +98 -0
  103. package/dist/{voice/Say.d.ts → registry/types.js} +4 -10
  104. package/dist/registry/workspaceKeys.d.ts +41 -0
  105. package/dist/registry/workspaceKeys.js +64 -0
  106. package/dist/session/index.d.ts +19 -0
  107. package/dist/session/index.js +35 -0
  108. package/dist/session/schema.d.ts +1951 -0
  109. package/dist/session/schema.js +397 -0
  110. package/dist/sessions/index.d.ts +93 -0
  111. package/dist/sessions/index.js +194 -0
  112. package/dist/telemetry.d.ts +76 -0
  113. package/dist/telemetry.js +360 -0
  114. package/dist/types.d.ts +3 -5
  115. package/dist/utils/{findIntegrationsCredentials.js → WriteBehind.d.ts} +13 -10
  116. package/dist/utils/WriteBehind.js +64 -0
  117. package/dist/utils/index.d.ts +1 -1
  118. package/dist/utils/index.js +1 -1
  119. package/dist/{voice/Play.d.ts → utils/logUnhandledRejections.d.ts} +8 -12
  120. package/dist/utils/logUnhandledRejections.js +37 -0
  121. package/dist/validators/calls.d.ts +2 -2
  122. package/dist/validators/calls.js +5 -2
  123. package/dist/validators/identity.d.ts +37 -8
  124. package/dist/validators/identity.js +22 -3
  125. package/dist/validators/index.d.ts +1 -0
  126. package/dist/validators/index.js +1 -0
  127. package/dist/validators/sip.d.ts +40 -0
  128. package/dist/validators/sip.js +99 -0
  129. package/dist/validators/sipnet/credentials.d.ts +2 -2
  130. package/dist/validators/sipnet/domains.js +7 -7
  131. package/dist/validators/sipnet/trunks.d.ts +14 -14
  132. package/dist/validators/sipnet/trunks.js +4 -2
  133. package/dist/webhooks/Dispatcher.d.ts +87 -0
  134. package/dist/webhooks/Dispatcher.js +225 -0
  135. package/dist/webhooks/bus.d.ts +49 -0
  136. package/dist/webhooks/bus.js +170 -0
  137. package/dist/webhooks/events.d.ts +66 -0
  138. package/dist/webhooks/events.js +62 -0
  139. package/dist/webhooks/index.d.ts +23 -0
  140. package/dist/webhooks/index.js +39 -0
  141. package/dist/webhooks/prisma.d.ts +55 -0
  142. package/dist/webhooks/prisma.js +242 -0
  143. package/dist/webhooks/signing.d.ts +8 -0
  144. package/dist/webhooks/signing.js +53 -0
  145. package/package.json +25 -12
  146. package/schema/session.v1.json +791 -0
  147. package/dist/assistants/assistantSchema.d.ts +0 -739
  148. package/dist/assistants/assistantSchema.js +0 -37
  149. package/dist/assistants/conversationSettingsSchema.d.ts +0 -122
  150. package/dist/assistants/conversationSettingsSchema.js +0 -133
  151. package/dist/assistants/eventsHookSchema.d.ts +0 -38
  152. package/dist/assistants/languageModelConfigSchema.d.ts +0 -206
  153. package/dist/assistants/languageModelConfigSchema.js +0 -91
  154. package/dist/identity/tokenHasAccessKeyId.d.ts +0 -2
  155. package/dist/protos/voice.proto +0 -557
  156. package/dist/voice/Dial.js +0 -20
  157. package/dist/voice/Gather.d.ts +0 -37
  158. package/dist/voice/Gather.js +0 -9
  159. package/dist/voice/Mute.d.ts +0 -31
  160. package/dist/voice/Mute.js +0 -9
  161. package/dist/voice/Play.js +0 -2
  162. package/dist/voice/PlayDtmf.js +0 -2
  163. package/dist/voice/PlaybackControl.d.ts +0 -31
  164. package/dist/voice/PlaybackControl.js +0 -11
  165. package/dist/voice/Record.d.ts +0 -36
  166. package/dist/voice/Record.js +0 -7
  167. package/dist/voice/Say.js +0 -2
  168. package/dist/voice/StopSay.js +0 -2
  169. package/dist/voice/Stream.d.ts +0 -53
  170. package/dist/voice/Stream.js +0 -19
  171. package/dist/voice/StreamGather.d.ts +0 -34
  172. package/dist/voice/StreamGather.js +0 -9
  173. package/dist/voice/Verb.d.ts +0 -27
  174. package/dist/voice/Verb.js +0 -2
  175. package/dist/voice/voice.d.ts +0 -171
  176. package/dist/voice/voice.js +0 -65
@@ -19,13 +19,10 @@
19
19
  import { ServingStatus } from "grpc-health-check";
20
20
  export declare const GRPC_NOT_SERVING_STATUS: ServingStatus;
21
21
  export declare const GRPC_SERVING_STATUS: ServingStatus;
22
- export declare const STASIS_APP_NAME = "mediacontroller";
23
- export declare const CALL_CONTEXT = "local-ctx-common";
24
- export declare const CALL_EXTENSION = "start";
25
22
  export declare const CALL_DETAIL_RECORD_MEASUREMENT = "cdr";
26
23
  export declare const INFLUXDB_CALLS_BUCKET = "calls";
27
24
  export declare const APP_REF_HEADER = "x-app-ref";
28
- export declare const ROUTR_DEFAULT_PEER_AOR = "sip:voice@default";
25
+ export declare const ROUTR_DEFAULT_PEER_AOR = "sip:medianode@default";
29
26
  export declare const AUTOPILOT_SPECIAL_LOCAL_ADDRESS = "autopilot.fonoster.local";
30
27
  export declare const AUTOPILOT_INTERNAL_ADDRESS = "autopilot:50061";
31
28
  export declare const WELCOME_DEMO_SPECIAL_LOCAL_ADDRESS = "welcome.demo.fonoster.local";
package/dist/constants.js CHANGED
@@ -1,15 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.WELCOME_DEMO_SPECIAL_LOCAL_ADDRESS = exports.AUTOPILOT_INTERNAL_ADDRESS = exports.AUTOPILOT_SPECIAL_LOCAL_ADDRESS = exports.ROUTR_DEFAULT_PEER_AOR = exports.APP_REF_HEADER = exports.INFLUXDB_CALLS_BUCKET = exports.CALL_DETAIL_RECORD_MEASUREMENT = exports.CALL_EXTENSION = exports.CALL_CONTEXT = exports.STASIS_APP_NAME = exports.GRPC_SERVING_STATUS = exports.GRPC_NOT_SERVING_STATUS = void 0;
3
+ exports.WELCOME_DEMO_SPECIAL_LOCAL_ADDRESS = exports.AUTOPILOT_INTERNAL_ADDRESS = exports.AUTOPILOT_SPECIAL_LOCAL_ADDRESS = exports.ROUTR_DEFAULT_PEER_AOR = exports.APP_REF_HEADER = exports.INFLUXDB_CALLS_BUCKET = exports.CALL_DETAIL_RECORD_MEASUREMENT = exports.GRPC_SERVING_STATUS = exports.GRPC_NOT_SERVING_STATUS = void 0;
4
4
  exports.GRPC_NOT_SERVING_STATUS = "NOT_SERVING";
5
5
  exports.GRPC_SERVING_STATUS = "SERVING";
6
- exports.STASIS_APP_NAME = "mediacontroller";
7
- exports.CALL_CONTEXT = "local-ctx-common";
8
- exports.CALL_EXTENSION = "start";
9
6
  exports.CALL_DETAIL_RECORD_MEASUREMENT = "cdr";
10
7
  exports.INFLUXDB_CALLS_BUCKET = "calls";
11
8
  exports.APP_REF_HEADER = "x-app-ref";
12
- exports.ROUTR_DEFAULT_PEER_AOR = "sip:voice@default";
9
+ // The peer app-linked numbers point at: the Go media nodes (least-sessions). Asterisk's
10
+ // sip:voice@default is gone (cleanbreak CB-B3).
11
+ exports.ROUTR_DEFAULT_PEER_AOR = "sip:medianode@default";
13
12
  exports.AUTOPILOT_SPECIAL_LOCAL_ADDRESS = "autopilot.fonoster.local";
14
13
  exports.AUTOPILOT_INTERNAL_ADDRESS = "autopilot:50061";
15
14
  exports.WELCOME_DEMO_SPECIAL_LOCAL_ADDRESS = "welcome.demo.fonoster.local";
package/dist/envs.d.ts CHANGED
@@ -1 +1,2 @@
1
- export declare const ROOT_DOMAIN: string;
1
+ export declare const setRootDomain: (domain: string) => void;
2
+ export declare const getRootDomain: () => string;
package/dist/envs.js CHANGED
@@ -1,9 +1,6 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.ROOT_DOMAIN = void 0;
3
+ exports.getRootDomain = exports.setRootDomain = void 0;
7
4
  /**
8
5
  * Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
9
6
  * http://github.com/fonoster/fonoster
@@ -22,10 +19,15 @@ exports.ROOT_DOMAIN = void 0;
22
19
  * See the License for the specific language governing permissions and
23
20
  * limitations under the License.
24
21
  */
25
- const path_1 = require("path");
26
- const dotenv_1 = __importDefault(require("dotenv"));
27
- if (process.env.NODE_ENV === "development") {
28
- dotenv_1.default.config({ path: (0, path_1.join)(__dirname, "..", "..", "..", ".env") });
29
- }
30
- const e = process.env;
31
- exports.ROOT_DOMAIN = e.APISERVER_ROOT_DOMAIN || e.ROOT_DOMAIN || "fonoster.local";
22
+ /**
23
+ * Root SIP domain that workspace domains must end with. The API server sets it
24
+ * from fonoster.yaml's `domain` at boot (setRootDomain); nothing reads it from
25
+ * the environment or a .env file (fix-config-and-placement-gaps).
26
+ */
27
+ let rootDomain = "fonoster.local";
28
+ const setRootDomain = (domain) => {
29
+ rootDomain = domain;
30
+ };
31
+ exports.setRootDomain = setRootDomain;
32
+ const getRootDomain = () => rootDomain;
33
+ exports.getRootDomain = getRootDomain;
@@ -1,15 +1,22 @@
1
1
  import { ServerInterceptingCall } from "@grpc/grpc-js";
2
+ import { TokenVerifyOptions } from "./isValidToken";
2
3
  /**
3
4
  * This function is a gRPC interceptor that checks if the request is valid
4
5
  * and if the user has the right permissions to access the resource. When
5
6
  * validating the request, the function will check if the request is in the
6
- * skip list, if the token is valid and if the role is allowed by the RBAC.
7
+ * skip list, if the token is a valid access token (RS256, unexpired,
8
+ * `tokenUse: "access"`, and the pinned issuer and audience when given) and if
9
+ * the caller's role in the target workspace is allowed by the RBAC.
10
+ *
11
+ * It fails closed: any error while checking a request answers
12
+ * UNAUTHENTICATED instead of reaching the handler or throwing.
7
13
  *
8
14
  * @param {string} identityPublicKey - The public key to validate the token
9
15
  * @param {string[]} publicPath - The list of public paths
16
+ * @param {TokenVerifyOptions} options - Issuer and audience to pin
10
17
  * @return {Function} - The gRPC interceptor
11
18
  */
12
- declare function createAuthInterceptor(identityPublicKey: string, publicPath: string[]): (methodDefinition: {
19
+ declare function createAuthInterceptor(identityPublicKey: string, publicPath: string[], options?: TokenVerifyOptions): (methodDefinition: {
13
20
  path: string;
14
21
  }, call: ServerInterceptingCall) => ServerInterceptingCall;
15
22
  export { createAuthInterceptor };
@@ -20,26 +20,29 @@ exports.createAuthInterceptor = createAuthInterceptor;
20
20
  * limitations under the License.
21
21
  */
22
22
  const logger_1 = require("@fonoster/logger");
23
- const decodeToken_1 = require("./decodeToken");
24
23
  const errors_1 = require("./errors");
25
24
  const getAccessKeyIdFromCall_1 = require("./getAccessKeyIdFromCall");
26
25
  const getTokenFromCall_1 = require("./getTokenFromCall");
27
26
  const hasAccess_1 = require("./hasAccess");
28
- const isValidToken_1 = require("./isValidToken");
29
- const roles_1 = require("./roles");
30
- const tokenHasAccessKeyId_1 = require("./tokenHasAccessKeyId");
27
+ const verifyAccessToken_1 = require("./verifyAccessToken");
31
28
  const logger = (0, logger_1.getLogger)({ service: "common", filePath: __filename });
32
29
  /**
33
30
  * This function is a gRPC interceptor that checks if the request is valid
34
31
  * and if the user has the right permissions to access the resource. When
35
32
  * validating the request, the function will check if the request is in the
36
- * skip list, if the token is valid and if the role is allowed by the RBAC.
33
+ * skip list, if the token is a valid access token (RS256, unexpired,
34
+ * `tokenUse: "access"`, and the pinned issuer and audience when given) and if
35
+ * the caller's role in the target workspace is allowed by the RBAC.
36
+ *
37
+ * It fails closed: any error while checking a request answers
38
+ * UNAUTHENTICATED instead of reaching the handler or throwing.
37
39
  *
38
40
  * @param {string} identityPublicKey - The public key to validate the token
39
41
  * @param {string[]} publicPath - The list of public paths
42
+ * @param {TokenVerifyOptions} options - Issuer and audience to pin
40
43
  * @return {Function} - The gRPC interceptor
41
44
  */
42
- function createAuthInterceptor(identityPublicKey, publicPath) {
45
+ function createAuthInterceptor(identityPublicKey, publicPath, options = {}) {
43
46
  /**
44
47
  * Inner function that will be called by the gRPC server.
45
48
  *
@@ -49,33 +52,36 @@ function createAuthInterceptor(identityPublicKey, publicPath) {
49
52
  * @return {ServerInterceptingCall} - The modified call object
50
53
  */
51
54
  return (methodDefinition, call) => {
52
- const { path } = methodDefinition;
53
- const accessKeyId = (0, getAccessKeyIdFromCall_1.getAccessKeyIdFromCall)(call);
54
- logger.verbose("intercepting api call to path", { accessKeyId, path });
55
- if (publicPath.includes(methodDefinition.path)) {
56
- logger.verbose("passing auth control to edge function", { path });
55
+ try {
56
+ const { path } = methodDefinition;
57
+ const accessKeyId = (0, getAccessKeyIdFromCall_1.getAccessKeyIdFromCall)(call);
58
+ logger.verbose("intercepting api call to path", { accessKeyId, path });
59
+ if (publicPath.includes(path)) {
60
+ logger.verbose("passing auth control to edge function", { path });
61
+ return call;
62
+ }
63
+ logger.verbose("validating token", { accessKeyId, path });
64
+ const decodedToken = (0, verifyAccessToken_1.verifyAccessToken)((0, getTokenFromCall_1.getTokenFromCall)(call), identityPublicKey, options);
65
+ if (!decodedToken) {
66
+ return (0, errors_1.unauthenticatedError)(call);
67
+ }
68
+ const allowed = (0, hasAccess_1.hasAccess)(decodedToken, path, accessKeyId);
69
+ logger.verbose("checking access for accessKeyId", {
70
+ accessKeyId,
71
+ path,
72
+ hasAccess: allowed
73
+ });
74
+ if (!allowed) {
75
+ return (0, errors_1.permissionDeniedError)(call);
76
+ }
57
77
  return call;
58
78
  }
59
- const token = (0, getTokenFromCall_1.getTokenFromCall)(call);
60
- logger.verbose("validating token", { accessKeyId, path });
61
- if (!(0, isValidToken_1.isValidToken)(token, identityPublicKey)) {
79
+ catch (error) {
80
+ logger.warn("refusing a call that failed the auth check", {
81
+ path: methodDefinition === null || methodDefinition === void 0 ? void 0 : methodDefinition.path,
82
+ error: error === null || error === void 0 ? void 0 : error.message
83
+ });
62
84
  return (0, errors_1.unauthenticatedError)(call);
63
85
  }
64
- const decodedToken = (0, decodeToken_1.decodeToken)(token);
65
- logger.verbose("checking access for accessKeyId", {
66
- accessKeyId,
67
- path,
68
- hasAccess: (0, hasAccess_1.hasAccess)(decodedToken, path),
69
- pathIsWorkspacePath: roles_1.workspaceResourceAccess.includes(path),
70
- tokenHasAccessKeyId: (0, tokenHasAccessKeyId_1.tokenHasAccessKeyId)(token, accessKeyId)
71
- });
72
- if (!(0, hasAccess_1.hasAccess)(decodedToken, path) ||
73
- (roles_1.workspaceResourceAccess.includes(path) &&
74
- !(0, tokenHasAccessKeyId_1.tokenHasAccessKeyId)(token, accessKeyId)) ||
75
- (roles_1.workspaceResourceOwnerOrAdminAccess.includes(path) &&
76
- !(0, tokenHasAccessKeyId_1.tokenHasAccessKeyId)(token, accessKeyId))) {
77
- return (0, errors_1.permissionDeniedError)(call);
78
- }
79
- return call;
80
86
  };
81
87
  }
@@ -1,6 +1,12 @@
1
1
  import { Access } from "./types";
2
+ /**
3
+ * Whether the token's roles allow the gRPC method. For a workspace method only
4
+ * the caller's role in the target workspace (`targetAccessKeyId`) counts, never
5
+ * a role held in another workspace; without a matching entry it is refused.
6
+ * Malformed claims are refused rather than thrown on.
7
+ */
2
8
  declare function hasAccess(decodedToken: {
3
- access: Access[];
4
- accessKeyId: string;
5
- }, method: string): boolean;
9
+ access?: Access[];
10
+ accessKeyId?: string;
11
+ }, method: string, targetAccessKeyId?: string): boolean;
6
12
  export { hasAccess };
@@ -21,12 +21,36 @@ exports.hasAccess = hasAccess;
21
21
  */
22
22
  const types_1 = require("@fonoster/types");
23
23
  const roles_1 = require("./roles");
24
- // This function only checks if the role has access to the grpc method
25
- function hasAccess(decodedToken, method) {
26
- const { access, accessKeyId } = decodedToken;
27
- const roleList = accessKeyId.startsWith("US") && // US is for user; user tokens only have USER role
28
- access.length === 0 // If it is a user token, and has no access, we still allow it in case it is a user method
29
- ? [types_1.Role.USER]
30
- : access.map((a) => a.role);
31
- return roleList.some((r) => roles_1.roles.find((role) => role.name === r && role.access.includes(method)));
24
+ /** Methods that act inside one workspace, named by the request's accessKeyId. */
25
+ const isWorkspaceMethod = (method) => roles_1.workspaceResourceAccess.includes(method) ||
26
+ roles_1.workspaceResourceOwnerOrAdminAccess.includes(method);
27
+ const roleAllows = (roleName, method) => roles_1.roles.some((role) => role.name === roleName && role.access.includes(method));
28
+ /**
29
+ * Whether the token's roles allow the gRPC method. For a workspace method only
30
+ * the caller's role in the target workspace (`targetAccessKeyId`) counts, never
31
+ * a role held in another workspace; without a matching entry it is refused.
32
+ * Malformed claims are refused rather than thrown on.
33
+ */
34
+ function hasAccess(decodedToken, method, targetAccessKeyId) {
35
+ const access = Array.isArray(decodedToken === null || decodedToken === void 0 ? void 0 : decodedToken.access)
36
+ ? decodedToken.access.filter((a) => a && typeof a.role === "string" && typeof a.accessKeyId === "string")
37
+ : [];
38
+ const accessKeyId = typeof (decodedToken === null || decodedToken === void 0 ? void 0 : decodedToken.accessKeyId) === "string"
39
+ ? decodedToken.accessKeyId
40
+ : "";
41
+ if (isWorkspaceMethod(method)) {
42
+ if (!targetAccessKeyId) {
43
+ return false;
44
+ }
45
+ const entry = access.find((a) => a.accessKeyId === targetAccessKeyId);
46
+ return entry ? roleAllows(entry.role, method) : false;
47
+ }
48
+ // User tokens (accessKeyId "US…") always carry the USER role, so methods
49
+ // about the user themselves (e.g. UpdateUser, CreateWorkspace) keep working
50
+ // once they belong to a workspace. Their workspace roles add to it.
51
+ const workspaceRoles = access.map((a) => a.role);
52
+ const roleList = accessKeyId.startsWith("US")
53
+ ? [types_1.Role.USER, ...workspaceRoles]
54
+ : workspaceRoles;
55
+ return roleList.some((r) => roleAllows(r, method));
32
56
  }
@@ -23,5 +23,6 @@ export * from "./getAccessKeyIdFromCall";
23
23
  export * from "./getTokenFromCall";
24
24
  export * from "./hasAccess";
25
25
  export * from "./isValidToken";
26
+ export * from "./verifyAccessToken";
26
27
  export * from "./types";
27
28
  export * from "./roles";
@@ -39,5 +39,6 @@ __exportStar(require("./getAccessKeyIdFromCall"), exports);
39
39
  __exportStar(require("./getTokenFromCall"), exports);
40
40
  __exportStar(require("./hasAccess"), exports);
41
41
  __exportStar(require("./isValidToken"), exports);
42
+ __exportStar(require("./verifyAccessToken"), exports);
42
43
  __exportStar(require("./types"), exports);
43
44
  __exportStar(require("./roles"), exports);
@@ -1,2 +1,12 @@
1
- declare function isValidToken(token: string, secret: string): boolean;
2
- export { isValidToken };
1
+ import jwt from "jsonwebtoken";
2
+ /** Identity signs every token with RS256; nothing else is accepted. */
3
+ declare const IDENTITY_SIGN_ALGORITHMS: jwt.Algorithm[];
4
+ /** Claims to pin when verifying; each one is checked only when given. */
5
+ type TokenVerifyOptions = {
6
+ issuer?: string;
7
+ audience?: string;
8
+ };
9
+ /** The verified claims, or undefined when the signature, expiry or claims don't check out. */
10
+ declare function verifyToken(token: string, secret: string, options?: TokenVerifyOptions): Record<string, unknown> | undefined;
11
+ declare function isValidToken(token: string, secret: string, options?: TokenVerifyOptions): boolean;
12
+ export { IDENTITY_SIGN_ALGORITHMS, isValidToken, TokenVerifyOptions, verifyToken };
@@ -3,7 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.IDENTITY_SIGN_ALGORITHMS = void 0;
6
7
  exports.isValidToken = isValidToken;
8
+ exports.verifyToken = verifyToken;
7
9
  /**
8
10
  * Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
9
11
  * http://github.com/fonoster/fonoster
@@ -26,26 +28,40 @@ const logger_1 = require("@fonoster/logger");
26
28
  const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
27
29
  const types_1 = require("./types");
28
30
  const logger = (0, logger_1.getLogger)({ service: "identity", filePath: __filename });
29
- function isValidToken(token, secret) {
31
+ /** Identity signs every token with RS256; nothing else is accepted. */
32
+ const IDENTITY_SIGN_ALGORITHMS = ["RS256"];
33
+ exports.IDENTITY_SIGN_ALGORITHMS = IDENTITY_SIGN_ALGORITHMS;
34
+ /** The verified claims, or undefined when the signature, expiry or claims don't check out. */
35
+ function verifyToken(token, secret, options = {}) {
36
+ if (!token || typeof token !== "string") {
37
+ return undefined;
38
+ }
30
39
  try {
31
- const decoded = jsonwebtoken_1.default.verify(token, secret);
40
+ const decoded = jsonwebtoken_1.default.verify(token, secret, Object.assign(Object.assign({ algorithms: IDENTITY_SIGN_ALGORITHMS }, (options.issuer ? { issuer: options.issuer } : {})), (options.audience ? { audience: options.audience } : {})));
41
+ if (!decoded || typeof decoded !== "object") {
42
+ return undefined;
43
+ }
44
+ const { exp } = decoded;
32
45
  const currentTime = Math.floor(Date.now() / 1000);
33
- if (decoded.exp <= currentTime) {
34
- logger.verbose("token expired", { exp: decoded.exp, currentTime });
35
- return false;
46
+ if (typeof exp !== "number" || exp <= currentTime) {
47
+ logger.verbose("token expired or without expiry", { exp, currentTime });
48
+ return undefined;
36
49
  }
37
- return true;
50
+ return decoded;
38
51
  }
39
52
  catch (error) {
40
53
  if (error.name === types_1.JsonWebErrorEnum.JsonWebTokenError) {
41
- logger.verbose("invalid JWT token", { token });
54
+ logger.verbose("invalid JWT token");
42
55
  }
43
56
  else if (error.name === types_1.JsonWebErrorEnum.TokenExpiredError) {
44
- logger.verbose("token expired", { token });
57
+ logger.verbose("token expired");
45
58
  }
46
59
  else {
47
60
  logger.verbose("unexpected JWT error:", error);
48
61
  }
49
- return false;
62
+ return undefined;
50
63
  }
51
64
  }
65
+ function isValidToken(token, secret, options = {}) {
66
+ return verifyToken(token, secret, options) !== undefined;
67
+ }
@@ -30,6 +30,18 @@ const workspaceResourceAccess = [
30
30
  "/fonoster.applications.v1beta2.Applications/DeleteApplication",
31
31
  "/fonoster.applications.v1beta2.Applications/ListApplications",
32
32
  "/fonoster.applications.v1beta2.Applications/EvaluateIntelligence",
33
+ "/fonoster.applications.v1beta2.Applications/RotateSigningSecret",
34
+ "/fonoster.webhooks.v1beta2.Webhooks/CreateWebhook",
35
+ "/fonoster.webhooks.v1beta2.Webhooks/ListWebhooks",
36
+ "/fonoster.webhooks.v1beta2.Webhooks/GetWebhook",
37
+ "/fonoster.webhooks.v1beta2.Webhooks/UpdateWebhook",
38
+ "/fonoster.webhooks.v1beta2.Webhooks/DeleteWebhook",
39
+ "/fonoster.webhooks.v1beta2.Webhooks/RotateWebhookSecret",
40
+ "/fonoster.webhooks.v1beta2.Webhooks/ListWebhookDeliveries",
41
+ "/fonoster.webhooks.v1beta2.Webhooks/ResendWebhookDelivery",
42
+ "/fonoster.providers.v1beta2.Providers/ListCatalog",
43
+ "/fonoster.providers.v1beta2.Providers/ListProviderKeys",
44
+ "/fonoster.providers.v1beta2.Providers/ListWarnings",
33
45
  "/fonoster.agents.v1beta2.Agents/CreateAgent",
34
46
  "/fonoster.agents.v1beta2.Agents/UpdateAgent",
35
47
  "/fonoster.agents.v1beta2.Agents/GetAgent",
@@ -69,18 +81,22 @@ const workspaceResourceAccess = [
69
81
  "/fonoster.calls.v1beta2.Calls/ListCalls",
70
82
  "/fonoster.calls.v1beta2.Calls/GetCall",
71
83
  "/fonoster.calls.v1beta2.Calls/TrackCall",
72
- "/fonoster.voice.v1beta2.Voice/CreateSession",
73
- "/fonoster.identity.v1beta2.Identity/CreateApiKey",
74
- "/fonoster.identity.v1beta2.Identity/DeleteApiKey",
75
- "/fonoster.identity.v1beta2.Identity/ListApiKeys",
76
- "/fonoster.identity.v1beta2.Identity/RegenerateApiKey"
84
+ "/fonoster.voice.v1beta2.Voice/CreateSession"
77
85
  ];
78
86
  exports.workspaceResourceAccess = workspaceResourceAccess;
79
87
  const workspaceResourceOwnerOrAdminAccess = [
88
+ // Bring-your-own vendor keys (add-provider-registry P1): owners and admins only.
89
+ "/fonoster.providers.v1beta2.Providers/SetProviderKey",
90
+ "/fonoster.providers.v1beta2.Providers/DeleteProviderKey",
80
91
  "/fonoster.identity.v1beta2.Identity/ListWorkspaceMembers",
81
92
  "/fonoster.identity.v1beta2.Identity/InviteUserToWorkspace",
82
93
  "/fonoster.identity.v1beta2.Identity/RemoveUserFromWorkspace",
83
- "/fonoster.identity.v1beta2.Identity/ResendWorkspaceMembershipInvitation"
94
+ "/fonoster.identity.v1beta2.Identity/ResendWorkspaceMembershipInvitation",
95
+ // API keys act with WORKSPACE_ADMIN rights, so only owners and admins manage them.
96
+ "/fonoster.identity.v1beta2.Identity/CreateApiKey",
97
+ "/fonoster.identity.v1beta2.Identity/DeleteApiKey",
98
+ "/fonoster.identity.v1beta2.Identity/ListApiKeys",
99
+ "/fonoster.identity.v1beta2.Identity/RegenerateApiKey"
84
100
  ];
85
101
  exports.workspaceResourceOwnerOrAdminAccess = workspaceResourceOwnerOrAdminAccess;
86
102
  const fullIdentityAccess = [
@@ -20,7 +20,8 @@ import { Role } from "@fonoster/types";
20
20
  declare enum TokenUseEnum {
21
21
  ID = "id",
22
22
  ACCESS = "access",
23
- REFRESH = "refresh"
23
+ REFRESH = "refresh",
24
+ INVITE = "invite"
24
25
  }
25
26
  declare enum JsonWebErrorEnum {
26
27
  JsonWebTokenError = "JsonWebTokenError",
@@ -49,6 +50,8 @@ type IdToken = BaseToken & {
49
50
  phoneNumberVerified: boolean;
50
51
  phoneNumber: string;
51
52
  email: string;
53
+ /** True while the user holds an invite's one-time password. */
54
+ mustChangePassword?: boolean;
52
55
  tokenUse: TokenUseEnum.ID;
53
56
  };
54
57
  type AccessToken = BaseToken & {
@@ -57,6 +60,8 @@ type AccessToken = BaseToken & {
57
60
  };
58
61
  type RefreshToken = BaseToken & {
59
62
  tokenUse: TokenUseEnum.REFRESH;
63
+ /** Unique id, tracked by Identity for rotation and revocation. */
64
+ jti?: string;
60
65
  };
61
66
  type DecodedToken<T extends TokenUseEnum> = T extends TokenUseEnum.ID ? IdToken : T extends TokenUseEnum.ACCESS ? AccessToken : T extends TokenUseEnum.REFRESH ? TokenUseEnum : never;
62
67
  export { Access, AccessToken, DecodedToken, IdToken, RefreshToken, RoleType, TokenUseEnum, JsonWebErrorEnum };
@@ -6,6 +6,7 @@ var TokenUseEnum;
6
6
  TokenUseEnum["ID"] = "id";
7
7
  TokenUseEnum["ACCESS"] = "access";
8
8
  TokenUseEnum["REFRESH"] = "refresh";
9
+ TokenUseEnum["INVITE"] = "invite";
9
10
  })(TokenUseEnum || (exports.TokenUseEnum = TokenUseEnum = {}));
10
11
  var JsonWebErrorEnum;
11
12
  (function (JsonWebErrorEnum) {
@@ -1,6 +1,3 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LanguageModelProvider = void 0;
4
1
  /**
5
2
  * Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
6
3
  * http://github.com/fonoster/fonoster
@@ -19,11 +16,13 @@ exports.LanguageModelProvider = void 0;
19
16
  * See the License for the specific language governing permissions and
20
17
  * limitations under the License.
21
18
  */
22
- var LanguageModelProvider;
23
- (function (LanguageModelProvider) {
24
- LanguageModelProvider["OPENAI"] = "openai";
25
- LanguageModelProvider["GROQ"] = "groq";
26
- LanguageModelProvider["OLLAMA"] = "ollama";
27
- LanguageModelProvider["GOOGLE"] = "google";
28
- LanguageModelProvider["ANTHROPIC"] = "anthropic";
29
- })(LanguageModelProvider || (exports.LanguageModelProvider = LanguageModelProvider = {}));
19
+ import { TokenVerifyOptions } from "./isValidToken";
20
+ import { AccessToken } from "./types";
21
+ /**
22
+ * Verifies `token` and returns it only when it is an access token: signed by
23
+ * Identity (RS256), unexpired, `tokenUse: "access"`, with a string
24
+ * `accessKeyId` and a well-formed `access` array. Id, refresh, invite and other
25
+ * signed tokens are refused (undefined), as is anything malformed.
26
+ */
27
+ declare function verifyAccessToken(token: string, identityPublicKey: string, options?: TokenVerifyOptions): AccessToken | undefined;
28
+ export { verifyAccessToken };
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.verifyAccessToken = verifyAccessToken;
4
+ /**
5
+ * Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
6
+ * http://github.com/fonoster/fonoster
7
+ *
8
+ * This file is part of Fonoster
9
+ *
10
+ * Licensed under the MIT License (the "License");
11
+ * you may not use this file except in compliance with
12
+ * the License. You may obtain a copy of the License at
13
+ *
14
+ * https://opensource.org/licenses/MIT
15
+ *
16
+ * Unless required by applicable law or agreed to in writing, software
17
+ * distributed under the License is distributed on an "AS IS" BASIS,
18
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19
+ * See the License for the specific language governing permissions and
20
+ * limitations under the License.
21
+ */
22
+ const isValidToken_1 = require("./isValidToken");
23
+ const types_1 = require("./types");
24
+ const isAccessEntry = (entry) => typeof entry === "object" &&
25
+ entry !== null &&
26
+ typeof entry.accessKeyId === "string" &&
27
+ typeof entry.role === "string";
28
+ /**
29
+ * Verifies `token` and returns it only when it is an access token: signed by
30
+ * Identity (RS256), unexpired, `tokenUse: "access"`, with a string
31
+ * `accessKeyId` and a well-formed `access` array. Id, refresh, invite and other
32
+ * signed tokens are refused (undefined), as is anything malformed.
33
+ */
34
+ function verifyAccessToken(token, identityPublicKey, options = {}) {
35
+ const claims = (0, isValidToken_1.verifyToken)(token, identityPublicKey, options);
36
+ if (!claims ||
37
+ claims.tokenUse !== types_1.TokenUseEnum.ACCESS ||
38
+ typeof claims.accessKeyId !== "string" ||
39
+ !Array.isArray(claims.access) ||
40
+ !claims.access.every(isAccessEntry)) {
41
+ return undefined;
42
+ }
43
+ return claims;
44
+ }
package/dist/index.d.ts CHANGED
@@ -17,6 +17,7 @@
17
17
  * limitations under the License.
18
18
  */
19
19
  export * from "./assistants";
20
+ export * from "./config";
20
21
  export * from "./GrpcError";
21
22
  export * from "./constants";
22
23
  export * from "./errors";
@@ -29,6 +30,15 @@ export * from "./types";
29
30
  export * from "./utils";
30
31
  export * as Validators from "./validators";
31
32
  export * from "./validators";
32
- export * from "./voice";
33
+ export * from "./calls";
33
34
  export * from "./countryIsoCodes";
34
35
  export * from "./identity";
36
+ export { getRootDomain, setRootDomain } from "./envs";
37
+ export * from "./session";
38
+ export * as Registry from "./registry";
39
+ export * as Webhooks from "./webhooks";
40
+ export * as CellSecrets from "./cellsecrets";
41
+ export * from "./telemetry";
42
+ export * from "./net";
43
+ export * as Sessions from "./sessions";
44
+ export * as RegionHub from "./regions/hub";
package/dist/index.js CHANGED
@@ -36,7 +36,7 @@ var __importStar = (this && this.__importStar) || (function () {
36
36
  };
37
37
  })();
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.Validators = exports.Messages = void 0;
39
+ exports.RegionHub = exports.Sessions = exports.CellSecrets = exports.Webhooks = exports.Registry = exports.setRootDomain = exports.getRootDomain = exports.Validators = exports.Messages = void 0;
40
40
  /**
41
41
  * Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
42
42
  * http://github.com/fonoster/fonoster
@@ -56,6 +56,7 @@ exports.Validators = exports.Messages = void 0;
56
56
  * limitations under the License.
57
57
  */
58
58
  __exportStar(require("./assistants"), exports);
59
+ __exportStar(require("./config"), exports);
59
60
  __exportStar(require("./GrpcError"), exports);
60
61
  __exportStar(require("./constants"), exports);
61
62
  __exportStar(require("./errors"), exports);
@@ -68,6 +69,17 @@ __exportStar(require("./types"), exports);
68
69
  __exportStar(require("./utils"), exports);
69
70
  exports.Validators = __importStar(require("./validators"));
70
71
  __exportStar(require("./validators"), exports);
71
- __exportStar(require("./voice"), exports);
72
+ __exportStar(require("./calls"), exports);
72
73
  __exportStar(require("./countryIsoCodes"), exports);
73
74
  __exportStar(require("./identity"), exports);
75
+ var envs_1 = require("./envs");
76
+ Object.defineProperty(exports, "getRootDomain", { enumerable: true, get: function () { return envs_1.getRootDomain; } });
77
+ Object.defineProperty(exports, "setRootDomain", { enumerable: true, get: function () { return envs_1.setRootDomain; } });
78
+ __exportStar(require("./session"), exports);
79
+ exports.Registry = __importStar(require("./registry"));
80
+ exports.Webhooks = __importStar(require("./webhooks"));
81
+ exports.CellSecrets = __importStar(require("./cellsecrets"));
82
+ __exportStar(require("./telemetry"), exports);
83
+ __exportStar(require("./net"), exports);
84
+ exports.Sessions = __importStar(require("./sessions"));
85
+ exports.RegionHub = __importStar(require("./regions/hub"));
@@ -0,0 +1,52 @@
1
+ import net from "node:net";
2
+ import { URL } from "node:url";
3
+ /**
4
+ * Outbound requests to tenant-chosen URLs (Autopilot HTTP tools, webhooks) must not reach the
5
+ * platform's own network (SSRF, RV-B2): http/https only, and every address the host resolves to
6
+ * must be public. Operators who self-host can allow internal destinations with
7
+ * `FONOSTER_ALLOWED_DESTINATIONS` (comma-separated host names, IPs or CIDRs; default empty).
8
+ */
9
+ export declare class UnsafeDestinationError extends Error {
10
+ constructor(message: string);
11
+ }
12
+ export type DestinationPolicy = {
13
+ /** Host names, IPs or CIDRs allowed although private; defaults to FONOSTER_ALLOWED_DESTINATIONS. */
14
+ allow?: string[];
15
+ };
16
+ /** True when the address is loopback, private, link-local, ULA, CGNAT, multicast or reserved. */
17
+ export declare function isPrivateAddress(ip: string): boolean;
18
+ /**
19
+ * Checks a tenant-chosen URL: http/https, not an internal name, and every resolved address
20
+ * public (or allowed by the operator). Returns the parsed URL.
21
+ */
22
+ export declare function assertPublicDestination(input: string | URL, policy?: DestinationPolicy): Promise<URL>;
23
+ /** Synchronous shape check (scheme, literal IPs, internal names) for request validation. */
24
+ export declare function assertPublicUrlShape(input: string | URL, policy?: DestinationPolicy): URL;
25
+ /**
26
+ * For WebSocket connections to tenant-chosen URLs (session apps, listeners): checks the URL's
27
+ * shape (ws/wss, no internal names or private literal IPs) and returns a socket `lookup` that
28
+ * refuses private addresses when the name resolves. Pass it as the `lookup` option of `ws`.
29
+ */
30
+ export declare function publicWebSocketGuard(input: string | URL, policy?: DestinationPolicy): {
31
+ lookup: net.LookupFunction;
32
+ };
33
+ /** Synchronous shape check of a ws:// or wss:// URL (see assertPublicUrlShape). */
34
+ export declare function assertPublicWebSocketUrlShape(input: string | URL, policy?: DestinationPolicy): URL;
35
+ export type SafeFetchInit = {
36
+ method?: string;
37
+ headers?: Record<string, string>;
38
+ body?: string;
39
+ signal?: AbortSignal;
40
+ /** Whole-request timeout (default 10 s). */
41
+ timeoutMs?: number;
42
+ /** Redirects followed, each re-checked (default 3). */
43
+ maxRedirects?: number;
44
+ /** Largest response body read (default 5 MiB). */
45
+ maxBytes?: number;
46
+ policy?: DestinationPolicy;
47
+ };
48
+ /**
49
+ * fetch for tenant-chosen URLs: checks the destination (and each redirect) with
50
+ * assertPublicDestination, re-checks the addresses the socket connects to, and times out.
51
+ */
52
+ export declare function safeFetch(input: string | URL, init?: SafeFetchInit): Promise<Response>;