@fonoster/authz 0.27.0 → 0.31.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.
- package/dist/client/AuthzClient.d.ts +7 -1
- package/dist/client/AuthzClient.js +30 -0
- package/dist/client/AuthzServiceClient.d.ts +5 -0
- package/dist/server/AuthzServer.d.ts +5 -1
- package/dist/server/AuthzServer.js +28 -8
- package/dist/server/DummyAuthzHandler.d.ts +2 -2
- package/dist/server/DummyAuthzHandler.js +1 -1
- package/dist/types.d.ts +24 -3
- package/dist/types.js +18 -0
- package/package.json +7 -8
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AddBillingMeterEventRequest, CheckMethodAuthorizedRequest, VoiceRequest } from "../types";
|
|
1
|
+
import { AddBillingMeterEventRequest, CheckMethodAuthorizedRequest, SessionDecision, VoiceRequest } from "../types";
|
|
2
2
|
/**
|
|
3
3
|
* AuthzClient class to interact with the AuthzServer via gRPC.
|
|
4
4
|
*/
|
|
@@ -15,6 +15,12 @@ export declare class AuthzClient {
|
|
|
15
15
|
* @returns Promise resolving to a boolean indicating authorization.
|
|
16
16
|
*/
|
|
17
17
|
checkSessionAuthorized(request: Partial<VoiceRequest>): Promise<boolean>;
|
|
18
|
+
/**
|
|
19
|
+
* Asks whether a session may start; a refusal names its reason (add-billing-service D7).
|
|
20
|
+
* @param request VoiceRequest containing session details.
|
|
21
|
+
* @param deadlineMs How long to wait for the answer before failing.
|
|
22
|
+
*/
|
|
23
|
+
checkSession(request: Partial<VoiceRequest>, deadlineMs?: number): Promise<SessionDecision>;
|
|
18
24
|
/**
|
|
19
25
|
* Checks if a specific method is authorized.
|
|
20
26
|
* @param request CheckMethodAuthorizedRequest containing accessKeyId and method.
|
|
@@ -85,6 +85,36 @@ class AuthzClient {
|
|
|
85
85
|
});
|
|
86
86
|
});
|
|
87
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Asks whether a session may start; a refusal names its reason (add-billing-service D7).
|
|
90
|
+
* @param request VoiceRequest containing session details.
|
|
91
|
+
* @param deadlineMs How long to wait for the answer before failing.
|
|
92
|
+
*/
|
|
93
|
+
async checkSession(request, deadlineMs) {
|
|
94
|
+
const options = deadlineMs
|
|
95
|
+
? { deadline: new Date(Date.now() + deadlineMs) }
|
|
96
|
+
: {};
|
|
97
|
+
return new Promise((resolve, reject) => {
|
|
98
|
+
this.client.checkSessionAuthorized(request, options, (error, response) => {
|
|
99
|
+
if (error) {
|
|
100
|
+
reject(new Error(`checkSession failed: ${error.message || error}`));
|
|
101
|
+
}
|
|
102
|
+
else if (response && typeof response.authorized === "boolean") {
|
|
103
|
+
resolve(response.authorized
|
|
104
|
+
? { authorized: true }
|
|
105
|
+
: {
|
|
106
|
+
authorized: false,
|
|
107
|
+
...(response.reason
|
|
108
|
+
? { reason: response.reason }
|
|
109
|
+
: {})
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
reject(new Error("checkSession failed: Invalid response format."));
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
}
|
|
88
118
|
/**
|
|
89
119
|
* Checks if a specific method is authorized.
|
|
90
120
|
* @param request CheckMethodAuthorizedRequest containing accessKeyId and method.
|
|
@@ -25,6 +25,11 @@ import { AddBillingMeterEventRequest, CheckMethodAuthorizedRequest, VoiceRequest
|
|
|
25
25
|
interface AuthzServiceClient extends grpc.Client {
|
|
26
26
|
checkSessionAuthorized(request: Partial<VoiceRequest>, callback: grpc.requestCallback<{
|
|
27
27
|
authorized: boolean;
|
|
28
|
+
reason?: string;
|
|
29
|
+
}>): void;
|
|
30
|
+
checkSessionAuthorized(request: Partial<VoiceRequest>, options: Partial<grpc.CallOptions>, callback: grpc.requestCallback<{
|
|
31
|
+
authorized: boolean;
|
|
32
|
+
reason?: string;
|
|
28
33
|
}>): void;
|
|
29
34
|
checkMethodAuthorized(request: CheckMethodAuthorizedRequest, callback: grpc.requestCallback<{
|
|
30
35
|
authorized: boolean;
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { AuthzHandler, ServerConfig } from "../types";
|
|
2
2
|
declare class AuthzServer {
|
|
3
3
|
config: ServerConfig;
|
|
4
|
+
private server?;
|
|
4
5
|
constructor(config?: ServerConfig);
|
|
5
|
-
|
|
6
|
+
/** Serves the handler; resolves with the bound port (undefined if binding failed). */
|
|
7
|
+
listen(handler: AuthzHandler): Promise<number | undefined>;
|
|
8
|
+
/** Stops serving at once. */
|
|
9
|
+
close(): void;
|
|
6
10
|
}
|
|
7
11
|
export { AuthzServer };
|
|
@@ -68,17 +68,25 @@ class AuthzServer {
|
|
|
68
68
|
constructor(config = defaultServerConfig_1.defaultServerConfig) {
|
|
69
69
|
this.config = (0, deepmerge_1.default)(defaultServerConfig_1.defaultServerConfig, config);
|
|
70
70
|
}
|
|
71
|
+
/** Serves the handler; resolves with the bound port (undefined if binding failed). */
|
|
71
72
|
async listen(handler) {
|
|
72
73
|
try {
|
|
73
74
|
const healthImpl = new grpc_health_check_1.HealthImplementation(common_1.statusMap);
|
|
74
75
|
const credentials = await (0, common_1.getServerCredentials)({});
|
|
75
76
|
const server = new grpc.Server();
|
|
77
|
+
this.server = server;
|
|
76
78
|
server.addService(serviceDefinition_1.serviceDefinition, {
|
|
77
79
|
checkSessionAuthorized: async (call, callback) => {
|
|
78
|
-
|
|
80
|
+
// Not the request itself: it carries the caller's token (M11).
|
|
81
|
+
logger.verbose("checkSessionAuthorized called", {
|
|
82
|
+
accessKeyId: call.request?.accessKeyId ?? call.request?.access_key_id,
|
|
83
|
+
appRef: call.request?.appRef ?? call.request?.app_ref
|
|
84
|
+
});
|
|
79
85
|
try {
|
|
80
|
-
const
|
|
81
|
-
callback(null,
|
|
86
|
+
const decision = await handler.checkSessionAuthorized(call.request);
|
|
87
|
+
callback(null, typeof decision === "boolean"
|
|
88
|
+
? { authorized: decision }
|
|
89
|
+
: { authorized: decision.authorized, reason: decision.reason });
|
|
82
90
|
}
|
|
83
91
|
catch (error) {
|
|
84
92
|
logger.error("error in checkSessionAuthorized:", error);
|
|
@@ -89,7 +97,11 @@ class AuthzServer {
|
|
|
89
97
|
}
|
|
90
98
|
},
|
|
91
99
|
checkMethodAuthorized: async (call, callback) => {
|
|
92
|
-
|
|
100
|
+
// Not the request itself: it carries the caller's token (M11).
|
|
101
|
+
logger.verbose("checkMethodAuthorized called", {
|
|
102
|
+
accessKeyId: call.request?.accessKeyId,
|
|
103
|
+
method: call.request?.method
|
|
104
|
+
});
|
|
93
105
|
try {
|
|
94
106
|
const authorized = await handler.checkMethodAuthorized(call.request);
|
|
95
107
|
callback(null, { authorized });
|
|
@@ -103,7 +115,9 @@ class AuthzServer {
|
|
|
103
115
|
}
|
|
104
116
|
},
|
|
105
117
|
addBillingMeterEvent: async (call, callback) => {
|
|
106
|
-
logger.verbose("addBillingMeterEvent called",
|
|
118
|
+
logger.verbose("addBillingMeterEvent called", {
|
|
119
|
+
accessKeyId: call.request?.accessKeyId
|
|
120
|
+
});
|
|
107
121
|
try {
|
|
108
122
|
const request = {
|
|
109
123
|
accessKeyId: call.request.accessKeyId,
|
|
@@ -123,18 +137,24 @@ class AuthzServer {
|
|
|
123
137
|
});
|
|
124
138
|
healthImpl.addToServer(server);
|
|
125
139
|
const bindAddr = `${this.config.bind}:${this.config.port}`;
|
|
126
|
-
server.bindAsync(bindAddr, credentials, async (err, port) => {
|
|
140
|
+
return await new Promise((resolve) => server.bindAsync(bindAddr, credentials, async (err, port) => {
|
|
127
141
|
if (err) {
|
|
128
142
|
logger.error("Failed to bind server:", err);
|
|
129
|
-
return;
|
|
143
|
+
return resolve(undefined);
|
|
130
144
|
}
|
|
131
145
|
healthImpl.setStatus("", common_1.GRPC_SERVING_STATUS);
|
|
132
146
|
logger.info(`Authz server started at ${this.config.bind}:${port}`);
|
|
133
|
-
|
|
147
|
+
resolve(port);
|
|
148
|
+
}));
|
|
134
149
|
}
|
|
135
150
|
catch (err) {
|
|
136
151
|
logger.error("Error starting AuthzServer:", err);
|
|
152
|
+
return undefined;
|
|
137
153
|
}
|
|
138
154
|
}
|
|
155
|
+
/** Stops serving at once. */
|
|
156
|
+
close() {
|
|
157
|
+
this.server?.forceShutdown();
|
|
158
|
+
}
|
|
139
159
|
}
|
|
140
160
|
exports.AuthzServer = AuthzServer;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { AddBillingMeterEventRequest, AuthzHandler, CheckMethodAuthorizedRequest, VoiceRequest } from "../types";
|
|
1
|
+
import { AddBillingMeterEventRequest, AuthzHandler, CheckMethodAuthorizedRequest, SessionDecision, VoiceRequest } from "../types";
|
|
2
2
|
declare class DummyAuthzHandler implements AuthzHandler {
|
|
3
|
-
checkSessionAuthorized(request: VoiceRequest): Promise<
|
|
3
|
+
checkSessionAuthorized(request: VoiceRequest): Promise<SessionDecision>;
|
|
4
4
|
checkMethodAuthorized(request: CheckMethodAuthorizedRequest): Promise<boolean>;
|
|
5
5
|
addBillingMeterEvent(request: AddBillingMeterEventRequest): Promise<void>;
|
|
6
6
|
}
|
|
@@ -24,7 +24,7 @@ const logger = (0, logger_1.getLogger)({ service: "authz", filePath: __filename
|
|
|
24
24
|
class DummyAuthzHandler {
|
|
25
25
|
async checkSessionAuthorized(request) {
|
|
26
26
|
logger.verbose("checkSessionAuthorized called", request);
|
|
27
|
-
return true;
|
|
27
|
+
return { authorized: true };
|
|
28
28
|
}
|
|
29
29
|
async checkMethodAuthorized(request) {
|
|
30
30
|
logger.verbose("checkMethodAuthorized called", request);
|
package/dist/types.d.ts
CHANGED
|
@@ -16,7 +16,20 @@
|
|
|
16
16
|
* See the License for the specific language governing permissions and
|
|
17
17
|
* limitations under the License.
|
|
18
18
|
*/
|
|
19
|
-
|
|
19
|
+
/** The call a session would serve (CheckSessionAuthorizedRequest in authz.proto). */
|
|
20
|
+
type VoiceRequest = {
|
|
21
|
+
appRef: string;
|
|
22
|
+
accessKeyId: string;
|
|
23
|
+
endpoint: string;
|
|
24
|
+
ingressNumber: string;
|
|
25
|
+
callerName: string;
|
|
26
|
+
callerNumber: string;
|
|
27
|
+
mediaSessionRef: string;
|
|
28
|
+
callRef: string;
|
|
29
|
+
sessionToken: string;
|
|
30
|
+
callDirection: "FROM_PSTN" | "TO_PSTN" | "INTRA_NETWORK";
|
|
31
|
+
metadata?: Record<string, string>;
|
|
32
|
+
};
|
|
20
33
|
type ServerConfig = {
|
|
21
34
|
bind?: string;
|
|
22
35
|
port?: number;
|
|
@@ -29,9 +42,17 @@ type AddBillingMeterEventRequest = {
|
|
|
29
42
|
accessKeyId: string;
|
|
30
43
|
payload: Record<string, unknown>;
|
|
31
44
|
};
|
|
45
|
+
/** Why a session was refused (add-billing-service D7; trial_ended: a Trial past its 30 days). */
|
|
46
|
+
type SessionRefusal = "insufficient_balance" | "concurrency_limit" | "trial_ended";
|
|
47
|
+
/** A session admission answer: a refusal names its reason. */
|
|
48
|
+
type SessionDecision = {
|
|
49
|
+
authorized: boolean;
|
|
50
|
+
reason?: SessionRefusal;
|
|
51
|
+
};
|
|
32
52
|
type AuthzHandler = {
|
|
33
|
-
|
|
53
|
+
/** A plain boolean is still accepted from handlers written before reasons existed. */
|
|
54
|
+
checkSessionAuthorized(request: VoiceRequest): Promise<boolean | SessionDecision>;
|
|
34
55
|
checkMethodAuthorized(request: CheckMethodAuthorizedRequest): Promise<boolean>;
|
|
35
56
|
addBillingMeterEvent(request: AddBillingMeterEventRequest): Promise<void>;
|
|
36
57
|
};
|
|
37
|
-
export { ServerConfig, AuthzHandler, VoiceRequest, CheckMethodAuthorizedRequest, AddBillingMeterEventRequest };
|
|
58
|
+
export { ServerConfig, AuthzHandler, SessionDecision, SessionRefusal, VoiceRequest, CheckMethodAuthorizedRequest, AddBillingMeterEventRequest };
|
package/dist/types.js
CHANGED
|
@@ -1,2 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
|
|
4
|
+
* http://github.com/fonoster/fonoster
|
|
5
|
+
*
|
|
6
|
+
* This file is part of Fonoster
|
|
7
|
+
*
|
|
8
|
+
* Licensed under the MIT License (the "License");
|
|
9
|
+
* you may not use this file except in compliance with
|
|
10
|
+
* the License. You may obtain a copy of the License at
|
|
11
|
+
*
|
|
12
|
+
* https://opensource.org/licenses/MIT
|
|
13
|
+
*
|
|
14
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
15
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
16
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
17
|
+
* See the License for the specific language governing permissions and
|
|
18
|
+
* limitations under the License.
|
|
19
|
+
*/
|
|
2
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fonoster/authz",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"description": "Authorization module for Fonoster",
|
|
5
5
|
"author": "Pedro Sanders <psanders@fonoster.com>",
|
|
6
6
|
"homepage": "https://github.com/fonoster/fonoster#readme",
|
|
@@ -14,8 +14,7 @@
|
|
|
14
14
|
"scripts": {
|
|
15
15
|
"prebuild": "rimraf ./dist tsconfig.tsbuildinfo",
|
|
16
16
|
"build": "tsc -b tsconfig.json",
|
|
17
|
-
"
|
|
18
|
-
"start:dev": "nodemon --exec tsx --watch src src/server/server.ts"
|
|
17
|
+
"dev": "nodemon --exec tsx --watch src src/server/server.ts"
|
|
19
18
|
},
|
|
20
19
|
"files": [
|
|
21
20
|
"dist"
|
|
@@ -24,10 +23,10 @@
|
|
|
24
23
|
"access": "public"
|
|
25
24
|
},
|
|
26
25
|
"dependencies": {
|
|
27
|
-
"@fonoster/common": "^0.
|
|
28
|
-
"@fonoster/identity": "^0.
|
|
29
|
-
"@fonoster/logger": "^0.
|
|
30
|
-
"@grpc/grpc-js": "~1.
|
|
26
|
+
"@fonoster/common": "^0.31.0",
|
|
27
|
+
"@fonoster/identity": "^0.31.0",
|
|
28
|
+
"@fonoster/logger": "^0.31.0",
|
|
29
|
+
"@grpc/grpc-js": "~1.14.6",
|
|
31
30
|
"deepmerge": "^4.3.1",
|
|
32
31
|
"grpc-health-check": "^2.0.2"
|
|
33
32
|
},
|
|
@@ -38,5 +37,5 @@
|
|
|
38
37
|
"bugs": {
|
|
39
38
|
"url": "https://github.com/fonoster/fonoster/issues"
|
|
40
39
|
},
|
|
41
|
-
"gitHead": "
|
|
40
|
+
"gitHead": "f0a5102dddeea0f70f659234108805bc10690f2e"
|
|
42
41
|
}
|