@serialsubscriptions/platform-integration 0.0.82 → 0.0.83
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/lib/SSISubscribedPlanApi.d.ts +11 -0
- package/lib/SSISubscribedPlanApi.js +30 -0
- package/lib/SubscribedPlanManager.d.ts +15 -2
- package/lib/SubscribedPlanManager.js +18 -2
- package/lib/auth.server.d.ts +18 -0
- package/lib/auth.server.js +44 -0
- package/lib/session/SessionManager.d.ts +1 -0
- package/lib/session/SessionManager.js +1 -0
- package/package.json +1 -1
|
@@ -256,6 +256,17 @@ export declare class SSISubscribedPlanApi {
|
|
|
256
256
|
* @throws Error if the request fails or entity is not found.
|
|
257
257
|
*/
|
|
258
258
|
get(uuid: string, options?: GetOptions): Promise<JsonApiDocument>;
|
|
259
|
+
/**
|
|
260
|
+
* Retrieves a single subscribed_plan entity by project ID.
|
|
261
|
+
* Fetches all subscribed plans (paginated), then returns the first one whose
|
|
262
|
+
* attributes.project_id matches the given project ID.
|
|
263
|
+
*
|
|
264
|
+
* @param projectId - The project ID (e.g. attributes.project_id from the API).
|
|
265
|
+
* @param options - Optional list options (include, fields). Pagination is handled internally.
|
|
266
|
+
* @returns Promise resolving to the JSON:API document with the subscribed plan, or null if not found.
|
|
267
|
+
* @throws Error if the request fails.
|
|
268
|
+
*/
|
|
269
|
+
getByProjectId(projectId: number, options?: Omit<ListOptions, 'pagination'>): Promise<JsonApiDocument | null>;
|
|
259
270
|
/**
|
|
260
271
|
* Creates a new subscribed_plan entity.
|
|
261
272
|
*
|
|
@@ -221,6 +221,36 @@ class SSISubscribedPlanApi {
|
|
|
221
221
|
const url = `${this.baseUrl}${this.apiBasePath}/${uuid}${queryParams.toString() ? `?${queryParams.toString()}` : ''}`;
|
|
222
222
|
return this.request('GET', url);
|
|
223
223
|
}
|
|
224
|
+
/**
|
|
225
|
+
* Retrieves a single subscribed_plan entity by project ID.
|
|
226
|
+
* Fetches all subscribed plans (paginated), then returns the first one whose
|
|
227
|
+
* attributes.project_id matches the given project ID.
|
|
228
|
+
*
|
|
229
|
+
* @param projectId - The project ID (e.g. attributes.project_id from the API).
|
|
230
|
+
* @param options - Optional list options (include, fields). Pagination is handled internally.
|
|
231
|
+
* @returns Promise resolving to the JSON:API document with the subscribed plan, or null if not found.
|
|
232
|
+
* @throws Error if the request fails.
|
|
233
|
+
*/
|
|
234
|
+
async getByProjectId(projectId, options = {}) {
|
|
235
|
+
const limit = 50;
|
|
236
|
+
let offset = 0;
|
|
237
|
+
while (true) {
|
|
238
|
+
const doc = await this.list({
|
|
239
|
+
...options,
|
|
240
|
+
pagination: { offset, limit },
|
|
241
|
+
});
|
|
242
|
+
const data = Array.isArray(doc.data) ? doc.data : doc.data ? [doc.data] : [];
|
|
243
|
+
const found = data.find((resource) => resource.attributes &&
|
|
244
|
+
resource.attributes.project_id === projectId);
|
|
245
|
+
if (found) {
|
|
246
|
+
return { data: found, included: doc.included, links: doc.links, meta: doc.meta };
|
|
247
|
+
}
|
|
248
|
+
if (data.length < limit) {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
offset += limit;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
224
254
|
/**
|
|
225
255
|
* Creates a new subscribed_plan entity.
|
|
226
256
|
*
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
* const domain = 'https://account.serialsubscriptionsdev.com';
|
|
13
13
|
* const manager = new SubscribedPlanManager(domain);
|
|
14
14
|
*
|
|
15
|
-
* //
|
|
16
|
-
* manager.setBearerToken(
|
|
15
|
+
* // Use the ACCESS_TOKEN from your session (not id_token). Set it before each request if the manager is shared.
|
|
16
|
+
* manager.setBearerToken(accessTokenFromSession);
|
|
17
17
|
*
|
|
18
18
|
* // Get all plans with features and limits
|
|
19
19
|
* const allPlans = await manager.getAllPlans();
|
|
@@ -24,6 +24,9 @@
|
|
|
24
24
|
* // Get plans by project ID
|
|
25
25
|
* const projectPlans = await manager.getPlansByProjectId(42);
|
|
26
26
|
*
|
|
27
|
+
* // Get single plan for a project (or null)
|
|
28
|
+
* const plan = await manager.getPlanForProject(42);
|
|
29
|
+
*
|
|
27
30
|
* // Get plans by user internal ID
|
|
28
31
|
* const userPlans = await manager.getPlansByUserId(3);
|
|
29
32
|
* ```
|
|
@@ -187,6 +190,9 @@ export declare class SubscribedPlanManager {
|
|
|
187
190
|
constructor(domain: string, options?: SubscribedPlanManagerOptions);
|
|
188
191
|
/**
|
|
189
192
|
* Set a shared Bearer token on all three underlying API clients.
|
|
193
|
+
* Use the OAuth access_token from your session (e.g. SessionManager.getSessionData(sessionId, 'access_token')).
|
|
194
|
+
* Do not use the id_token — the JSON:API expects the access_token for authorization.
|
|
195
|
+
* If the manager is shared across requests, call this with the current request's access_token before each API call.
|
|
190
196
|
*/
|
|
191
197
|
setBearerToken(token: string): void;
|
|
192
198
|
/**
|
|
@@ -205,6 +211,13 @@ export declare class SubscribedPlanManager {
|
|
|
205
211
|
* Uses: ?filter[project_id]={project_int_id}
|
|
206
212
|
*/
|
|
207
213
|
getPlansByProjectId(projectId: number): Promise<SubscribedPlan[]>;
|
|
214
|
+
/**
|
|
215
|
+
* Get the single subscribed plan for a given project_id (with features and limits).
|
|
216
|
+
* Returns the first matching plan, or null if none. Use when at most one plan per project.
|
|
217
|
+
*
|
|
218
|
+
* Uses: ?filter[project_id]={project_int_id}
|
|
219
|
+
*/
|
|
220
|
+
getPlanForProject(projectId: number): Promise<SubscribedPlan | null>;
|
|
208
221
|
/**
|
|
209
222
|
* Filter an array of plans by project_id.
|
|
210
223
|
*
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* const domain = 'https://account.serialsubscriptionsdev.com';
|
|
14
14
|
* const manager = new SubscribedPlanManager(domain);
|
|
15
15
|
*
|
|
16
|
-
* //
|
|
17
|
-
* manager.setBearerToken(
|
|
16
|
+
* // Use the ACCESS_TOKEN from your session (not id_token). Set it before each request if the manager is shared.
|
|
17
|
+
* manager.setBearerToken(accessTokenFromSession);
|
|
18
18
|
*
|
|
19
19
|
* // Get all plans with features and limits
|
|
20
20
|
* const allPlans = await manager.getAllPlans();
|
|
@@ -25,6 +25,9 @@
|
|
|
25
25
|
* // Get plans by project ID
|
|
26
26
|
* const projectPlans = await manager.getPlansByProjectId(42);
|
|
27
27
|
*
|
|
28
|
+
* // Get single plan for a project (or null)
|
|
29
|
+
* const plan = await manager.getPlanForProject(42);
|
|
30
|
+
*
|
|
28
31
|
* // Get plans by user internal ID
|
|
29
32
|
* const userPlans = await manager.getPlansByUserId(3);
|
|
30
33
|
* ```
|
|
@@ -66,6 +69,9 @@ class SubscribedPlanManager {
|
|
|
66
69
|
}
|
|
67
70
|
/**
|
|
68
71
|
* Set a shared Bearer token on all three underlying API clients.
|
|
72
|
+
* Use the OAuth access_token from your session (e.g. SessionManager.getSessionData(sessionId, 'access_token')).
|
|
73
|
+
* Do not use the id_token — the JSON:API expects the access_token for authorization.
|
|
74
|
+
* If the manager is shared across requests, call this with the current request's access_token before each API call.
|
|
69
75
|
*/
|
|
70
76
|
setBearerToken(token) {
|
|
71
77
|
this.planApi.setBearerToken(token);
|
|
@@ -110,6 +116,16 @@ class SubscribedPlanManager {
|
|
|
110
116
|
});
|
|
111
117
|
return this.hydratePlans(doc);
|
|
112
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Get the single subscribed plan for a given project_id (with features and limits).
|
|
121
|
+
* Returns the first matching plan, or null if none. Use when at most one plan per project.
|
|
122
|
+
*
|
|
123
|
+
* Uses: ?filter[project_id]={project_int_id}
|
|
124
|
+
*/
|
|
125
|
+
async getPlanForProject(projectId) {
|
|
126
|
+
const plans = await this.getPlansByProjectId(projectId);
|
|
127
|
+
return plans[0] ?? null;
|
|
128
|
+
}
|
|
113
129
|
/**
|
|
114
130
|
* Filter an array of plans by project_id.
|
|
115
131
|
*
|
package/lib/auth.server.d.ts
CHANGED
|
@@ -2,6 +2,24 @@ import { SSIStorage } from "./storage/SSIStorage";
|
|
|
2
2
|
export declare const scopes: {
|
|
3
3
|
readonly defaultScopes: "openid profile email view_project create_project delete_project update_project view_subscribed_plan view_subscribed_feature view_subscribed_limit access_subscription_usage";
|
|
4
4
|
};
|
|
5
|
+
/**
|
|
6
|
+
* Check if a JWT is expired (or within bufferSeconds of expiry) by decoding its payload.
|
|
7
|
+
* Does not verify the signature — use only for expiry checks (e.g. "should I refresh?").
|
|
8
|
+
* For authorization, use AuthServer.verifyJwt() or verifyAndDecodeJwt().
|
|
9
|
+
*
|
|
10
|
+
* @param jwt - The JWT string (e.g. access_token or id_token from session).
|
|
11
|
+
* @param bufferSeconds - If provided, treat the token as "expired" when it would expire within this many seconds (default: 0).
|
|
12
|
+
* @returns true if the token is expired (or expiring within bufferSeconds), or if the JWT is missing/invalid; false otherwise.
|
|
13
|
+
*/
|
|
14
|
+
export declare function isJwtExpired(jwt: string, bufferSeconds?: number): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Get the number of seconds until a JWT expires, by decoding its payload.
|
|
17
|
+
* Does not verify the signature. For authorization use AuthServer.verifyJwt().
|
|
18
|
+
*
|
|
19
|
+
* @param jwt - The JWT string.
|
|
20
|
+
* @returns Seconds until expiry (0 if already expired), or null if the JWT has no exp claim or is invalid.
|
|
21
|
+
*/
|
|
22
|
+
export declare function getJwtExpirySeconds(jwt: string): number | null;
|
|
5
23
|
export type AuthConfig = {
|
|
6
24
|
issuerBaseUrl?: string;
|
|
7
25
|
authorizationEndpoint?: string;
|
package/lib/auth.server.js
CHANGED
|
@@ -36,6 +36,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
36
36
|
})();
|
|
37
37
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
38
|
exports.AuthServer = exports.scopes = void 0;
|
|
39
|
+
exports.isJwtExpired = isJwtExpired;
|
|
40
|
+
exports.getJwtExpirySeconds = getJwtExpirySeconds;
|
|
39
41
|
const crypto = __importStar(require("crypto"));
|
|
40
42
|
const jose_1 = require("jose");
|
|
41
43
|
const stateStore_1 = require("./stateStore");
|
|
@@ -48,6 +50,48 @@ exports.scopes = {
|
|
|
48
50
|
function normalizeIssuer(url) {
|
|
49
51
|
return url.replace(/\/+$/, "");
|
|
50
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Check if a JWT is expired (or within bufferSeconds of expiry) by decoding its payload.
|
|
55
|
+
* Does not verify the signature — use only for expiry checks (e.g. "should I refresh?").
|
|
56
|
+
* For authorization, use AuthServer.verifyJwt() or verifyAndDecodeJwt().
|
|
57
|
+
*
|
|
58
|
+
* @param jwt - The JWT string (e.g. access_token or id_token from session).
|
|
59
|
+
* @param bufferSeconds - If provided, treat the token as "expired" when it would expire within this many seconds (default: 0).
|
|
60
|
+
* @returns true if the token is expired (or expiring within bufferSeconds), or if the JWT is missing/invalid; false otherwise.
|
|
61
|
+
*/
|
|
62
|
+
function isJwtExpired(jwt, bufferSeconds = 0) {
|
|
63
|
+
try {
|
|
64
|
+
const payload = (0, jose_1.decodeJwt)(jwt);
|
|
65
|
+
const exp = payload.exp;
|
|
66
|
+
if (typeof exp !== "number")
|
|
67
|
+
return false; // no exp claim → not expired
|
|
68
|
+
const now = Math.floor(Date.now() / 1000);
|
|
69
|
+
return exp <= now + bufferSeconds;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return true; // malformed or missing → treat as expired
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Get the number of seconds until a JWT expires, by decoding its payload.
|
|
77
|
+
* Does not verify the signature. For authorization use AuthServer.verifyJwt().
|
|
78
|
+
*
|
|
79
|
+
* @param jwt - The JWT string.
|
|
80
|
+
* @returns Seconds until expiry (0 if already expired), or null if the JWT has no exp claim or is invalid.
|
|
81
|
+
*/
|
|
82
|
+
function getJwtExpirySeconds(jwt) {
|
|
83
|
+
try {
|
|
84
|
+
const payload = (0, jose_1.decodeJwt)(jwt);
|
|
85
|
+
const exp = payload.exp;
|
|
86
|
+
if (typeof exp !== "number")
|
|
87
|
+
return null;
|
|
88
|
+
const now = Math.floor(Date.now() / 1000);
|
|
89
|
+
return Math.max(0, exp - now);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
51
95
|
class AuthServer {
|
|
52
96
|
constructor(config) {
|
|
53
97
|
// Use empty object as default if config is not provided
|
|
@@ -8,6 +8,7 @@ exports.sessionRoles = {
|
|
|
8
8
|
allRoles: ['platform_admin', 'member', 'owner', 'admin', 'billing', 'readonly'],
|
|
9
9
|
adminRoles: ['platform_admin', 'owner', 'admin'],
|
|
10
10
|
userRoles: ['member', 'owner', 'admin', 'billing', 'readonly'],
|
|
11
|
+
userWriteRoles: ['member', 'owner', 'admin'],
|
|
11
12
|
userAdminRoles: ['platform_admin', 'owner', 'admin']
|
|
12
13
|
};
|
|
13
14
|
/** Defaults & env */
|