@serialsubscriptions/platform-integration 0.0.8-5.1
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/README.md +1 -0
- package/lib/SSIProject.d.ts +343 -0
- package/lib/SSIProject.js +429 -0
- package/lib/SSIProjectApi.d.ts +384 -0
- package/lib/SSIProjectApi.js +534 -0
- package/lib/SSISubscribedFeatureApi.d.ts +387 -0
- package/lib/SSISubscribedFeatureApi.js +511 -0
- package/lib/SSISubscribedLimitApi.d.ts +384 -0
- package/lib/SSISubscribedLimitApi.js +534 -0
- package/lib/SSISubscribedPlanApi.d.ts +395 -0
- package/lib/SSISubscribedPlanApi.js +567 -0
- package/lib/SubscribedPlanManager.d.ts +400 -0
- package/lib/SubscribedPlanManager.js +319 -0
- package/lib/UsageApi.d.ts +128 -0
- package/lib/UsageApi.js +224 -0
- package/lib/auth.server.d.ts +212 -0
- package/lib/auth.server.js +624 -0
- package/lib/cache/SSICache.d.ts +40 -0
- package/lib/cache/SSICache.js +134 -0
- package/lib/cache/backends/MemoryCacheBackend.d.ts +15 -0
- package/lib/cache/backends/MemoryCacheBackend.js +46 -0
- package/lib/cache/backends/RedisCacheBackend.d.ts +27 -0
- package/lib/cache/backends/RedisCacheBackend.js +95 -0
- package/lib/cache/constants.d.ts +7 -0
- package/lib/cache/constants.js +10 -0
- package/lib/cache/types.d.ts +27 -0
- package/lib/cache/types.js +2 -0
- package/lib/clientConfig.d.ts +42 -0
- package/lib/clientConfig.js +80 -0
- package/lib/frontend/index.d.ts +3 -0
- package/lib/frontend/index.js +12 -0
- package/lib/frontend/session/SessionClient.d.ts +36 -0
- package/lib/frontend/session/SessionClient.js +151 -0
- package/lib/index.d.ts +17 -0
- package/lib/index.js +40 -0
- package/lib/lib/session/SessionClient.d.ts +11 -0
- package/lib/lib/session/SessionClient.js +47 -0
- package/lib/lib/session/index.d.ts +3 -0
- package/lib/lib/session/index.js +3 -0
- package/lib/lib/session/stores/MemoryStore.d.ts +7 -0
- package/lib/lib/session/stores/MemoryStore.js +23 -0
- package/lib/lib/session/stores/index.d.ts +1 -0
- package/lib/lib/session/stores/index.js +1 -0
- package/lib/lib/session/types.d.ts +37 -0
- package/lib/lib/session/types.js +1 -0
- package/lib/requestConfig.d.ts +60 -0
- package/lib/requestConfig.js +151 -0
- package/lib/session/SessionClient.d.ts +19 -0
- package/lib/session/SessionClient.js +132 -0
- package/lib/session/SessionManager.d.ts +142 -0
- package/lib/session/SessionManager.js +437 -0
- package/lib/stateStore.d.ts +5 -0
- package/lib/stateStore.js +9 -0
- package/lib/storage/SSIStorage.d.ts +24 -0
- package/lib/storage/SSIStorage.js +117 -0
- package/lib/storage/backends/MemoryBackend.d.ts +10 -0
- package/lib/storage/backends/MemoryBackend.js +44 -0
- package/lib/storage/backends/PostgresBackend.d.ts +24 -0
- package/lib/storage/backends/PostgresBackend.js +106 -0
- package/lib/storage/backends/RedisBackend.d.ts +19 -0
- package/lib/storage/backends/RedisBackend.js +78 -0
- package/lib/storage/types.d.ts +27 -0
- package/lib/storage/types.js +2 -0
- package/package.json +74 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SessionClient = void 0;
|
|
4
|
+
const clientConfig_1 = require("../../clientConfig");
|
|
5
|
+
/** Session cookie name; must match SessionManager (SSI_COOKIE_NAME env or default). */
|
|
6
|
+
const SESSION_COOKIE_NAME = process.env.SSI_COOKIE_NAME ?? "ssi_session";
|
|
7
|
+
class SessionClient {
|
|
8
|
+
static normalizeBaseUrl(baseUrl) {
|
|
9
|
+
if (!baseUrl) {
|
|
10
|
+
return "";
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
const url = new URL(baseUrl);
|
|
14
|
+
// Remove trailing slash and default ports
|
|
15
|
+
url.pathname = url.pathname.replace(/\/$/, "");
|
|
16
|
+
if ((url.protocol === "https:" && url.port === "443") || (url.protocol === "http:" && url.port === "80")) {
|
|
17
|
+
url.port = "";
|
|
18
|
+
}
|
|
19
|
+
return url.toString().replace(/\/$/, "");
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return baseUrl;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
constructor(baseUrl, cookieHeader) {
|
|
26
|
+
this.cookieHeader = cookieHeader;
|
|
27
|
+
this.claims = null;
|
|
28
|
+
this.ttl = null;
|
|
29
|
+
const resolvedBaseUrl = baseUrl ?? process.env.NEXT_PUBLIC_BASE_URL ?? "";
|
|
30
|
+
this.baseUrl = SessionClient.normalizeBaseUrl(resolvedBaseUrl);
|
|
31
|
+
if (!this.baseUrl || !this.baseUrl.startsWith("http://") && !this.baseUrl.startsWith("https://")) {
|
|
32
|
+
console.error(`You must set baseUrl to SessionClient or you must set env variable NEXT_PUBLIC_BASE_URL. Incorrect value \`${resolvedBaseUrl}\``);
|
|
33
|
+
}
|
|
34
|
+
this.initPromise = this.loadClaims();
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Returns a new SessionClient for this request. Call once per request (or per user
|
|
38
|
+
* context) so session is not shared across requests.
|
|
39
|
+
*
|
|
40
|
+
* Accepts a base URL string or an SSIClientConfig (e.g. from getClientConfig());
|
|
41
|
+
* when given config, uses config.apiUrl ?? config.baseUrl.
|
|
42
|
+
*
|
|
43
|
+
* In Next.js server components/route handlers you can omit cookieHeader: the client
|
|
44
|
+
* will automatically use the current request's session cookie (via next/headers).
|
|
45
|
+
* In other server contexts (e.g. middleware, non-Next), pass the request's Cookie
|
|
46
|
+
* header so this client is bound to that request's session.
|
|
47
|
+
*/
|
|
48
|
+
static getSessionClient(baseUrlOrConfig, cookieHeader) {
|
|
49
|
+
const baseUrl = typeof baseUrlOrConfig === 'string' ? baseUrlOrConfig : (0, clientConfig_1.getSessionClientBaseUrl)(baseUrlOrConfig);
|
|
50
|
+
return new SessionClient(baseUrl, cookieHeader);
|
|
51
|
+
}
|
|
52
|
+
async isLoggedIn() {
|
|
53
|
+
await this.initPromise;
|
|
54
|
+
return this.claims != null;
|
|
55
|
+
}
|
|
56
|
+
// Non-awaiting stub to ensure initialization happens without blocking
|
|
57
|
+
warmup() {
|
|
58
|
+
void this.initPromise;
|
|
59
|
+
}
|
|
60
|
+
async getTtl() {
|
|
61
|
+
await this.initPromise;
|
|
62
|
+
return this.ttl;
|
|
63
|
+
}
|
|
64
|
+
async get(claimName) {
|
|
65
|
+
await this.initPromise;
|
|
66
|
+
if (this.claims && typeof this.claims === "object") {
|
|
67
|
+
const record = this.claims;
|
|
68
|
+
return record[claimName];
|
|
69
|
+
}
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Get a specific claim from the session.
|
|
74
|
+
* Alias for `get()` method for consistency with SessionManager.
|
|
75
|
+
*/
|
|
76
|
+
async getClaim(claimName) {
|
|
77
|
+
return this.get(claimName);
|
|
78
|
+
}
|
|
79
|
+
async getAll() {
|
|
80
|
+
await this.initPromise;
|
|
81
|
+
return this.claims;
|
|
82
|
+
}
|
|
83
|
+
async withSession(init = {}) {
|
|
84
|
+
if (this.cookieHeader) {
|
|
85
|
+
const headers = new Headers(init.headers ?? {});
|
|
86
|
+
if (!headers.has("Cookie")) {
|
|
87
|
+
headers.set("Cookie", this.cookieHeader);
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
...init,
|
|
91
|
+
headers,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
if (typeof window !== "undefined") {
|
|
95
|
+
return init;
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
// Dynamic import of next/headers - only available in Next.js server context (request-scoped)
|
|
99
|
+
const { cookies } = await import("next/headers");
|
|
100
|
+
const cookieStore = await cookies();
|
|
101
|
+
const sessionCookie = cookieStore.get(SESSION_COOKIE_NAME);
|
|
102
|
+
const serializedCookies = sessionCookie
|
|
103
|
+
? `${sessionCookie.name}=${sessionCookie.value}`
|
|
104
|
+
: "";
|
|
105
|
+
if (!serializedCookies) {
|
|
106
|
+
return init;
|
|
107
|
+
}
|
|
108
|
+
const headers = new Headers(init.headers ?? {});
|
|
109
|
+
if (!headers.has("Cookie")) {
|
|
110
|
+
headers.set("Cookie", serializedCookies);
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
...init,
|
|
114
|
+
headers,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return init;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
async loadClaims() {
|
|
122
|
+
try {
|
|
123
|
+
const url = new URL("/api/v1/auth/session", this.baseUrl).toString();
|
|
124
|
+
const response = await fetch(url, await this.withSession({
|
|
125
|
+
method: "GET",
|
|
126
|
+
headers: { accept: "application/json" },
|
|
127
|
+
credentials: "include",
|
|
128
|
+
}));
|
|
129
|
+
if (!response.ok) {
|
|
130
|
+
this.claims = null;
|
|
131
|
+
this.ttl = null;
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const data = (await response.json());
|
|
135
|
+
if (data.ok) {
|
|
136
|
+
this.claims = data.claims ?? null;
|
|
137
|
+
this.ttl = typeof data.ttl_seconds === "number" ? data.ttl_seconds : null;
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
this.claims = null;
|
|
141
|
+
this.ttl = null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch (_err) {
|
|
145
|
+
this.claims = null;
|
|
146
|
+
this.ttl = null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
exports.SessionClient = SessionClient;
|
|
151
|
+
exports.default = SessionClient;
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export * from './auth.server';
|
|
2
|
+
export * from './requestConfig';
|
|
3
|
+
export * from './clientConfig';
|
|
4
|
+
export * from './stateStore';
|
|
5
|
+
export * from './storage/SSIStorage';
|
|
6
|
+
export * from './storage/types';
|
|
7
|
+
export * from './session/SessionManager';
|
|
8
|
+
export { SessionClient } from './frontend/session/SessionClient';
|
|
9
|
+
export * from './cache/SSICache';
|
|
10
|
+
export * from './cache/types';
|
|
11
|
+
export * from './cache/constants';
|
|
12
|
+
export * from './SSIProjectApi';
|
|
13
|
+
export { SSISubscribedFeatureApi } from './SSISubscribedFeatureApi';
|
|
14
|
+
export { SSISubscribedLimitApi } from './SSISubscribedLimitApi';
|
|
15
|
+
export { SSISubscribedPlanApi } from './SSISubscribedPlanApi';
|
|
16
|
+
export * from './SubscribedPlanManager';
|
|
17
|
+
export * from './UsageApi';
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.SSISubscribedPlanApi = exports.SSISubscribedLimitApi = exports.SSISubscribedFeatureApi = exports.SessionClient = void 0;
|
|
18
|
+
__exportStar(require("./auth.server"), exports);
|
|
19
|
+
__exportStar(require("./requestConfig"), exports);
|
|
20
|
+
__exportStar(require("./clientConfig"), exports);
|
|
21
|
+
__exportStar(require("./stateStore"), exports);
|
|
22
|
+
__exportStar(require("./storage/SSIStorage"), exports);
|
|
23
|
+
__exportStar(require("./storage/types"), exports);
|
|
24
|
+
__exportStar(require("./session/SessionManager"), exports);
|
|
25
|
+
var SessionClient_1 = require("./frontend/session/SessionClient");
|
|
26
|
+
Object.defineProperty(exports, "SessionClient", { enumerable: true, get: function () { return SessionClient_1.SessionClient; } });
|
|
27
|
+
__exportStar(require("./cache/SSICache"), exports);
|
|
28
|
+
__exportStar(require("./cache/types"), exports);
|
|
29
|
+
__exportStar(require("./cache/constants"), exports);
|
|
30
|
+
// Export all types and classes from SSIProjectApi (includes shared JSON:API types)
|
|
31
|
+
__exportStar(require("./SSIProjectApi"), exports);
|
|
32
|
+
// Export only the unique classes from subscribed APIs (types are already exported from SSIProjectApi)
|
|
33
|
+
var SSISubscribedFeatureApi_1 = require("./SSISubscribedFeatureApi");
|
|
34
|
+
Object.defineProperty(exports, "SSISubscribedFeatureApi", { enumerable: true, get: function () { return SSISubscribedFeatureApi_1.SSISubscribedFeatureApi; } });
|
|
35
|
+
var SSISubscribedLimitApi_1 = require("./SSISubscribedLimitApi");
|
|
36
|
+
Object.defineProperty(exports, "SSISubscribedLimitApi", { enumerable: true, get: function () { return SSISubscribedLimitApi_1.SSISubscribedLimitApi; } });
|
|
37
|
+
var SSISubscribedPlanApi_1 = require("./SSISubscribedPlanApi");
|
|
38
|
+
Object.defineProperty(exports, "SSISubscribedPlanApi", { enumerable: true, get: function () { return SSISubscribedPlanApi_1.SSISubscribedPlanApi; } });
|
|
39
|
+
__exportStar(require("./SubscribedPlanManager"), exports);
|
|
40
|
+
__exportStar(require("./UsageApi"), exports);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { SessionClientInit, SessionSnapshot, TokenBundle } from './types';
|
|
2
|
+
export declare class SessionClient {
|
|
3
|
+
private readonly init;
|
|
4
|
+
constructor(init: SessionClientInit);
|
|
5
|
+
private k;
|
|
6
|
+
getAll(sessionId: string): Promise<SessionSnapshot>;
|
|
7
|
+
setTokens(sessionId: string, t: TokenBundle): Promise<void>;
|
|
8
|
+
getTtl(_sessionId: string): Promise<number | null>;
|
|
9
|
+
clear(sessionId: string): Promise<void>;
|
|
10
|
+
maybeRefresh(_sessionId: string): Promise<boolean>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const ACCESS = 'access';
|
|
2
|
+
const ID = 'id';
|
|
3
|
+
const REFRESH = 'refresh';
|
|
4
|
+
const CLAIMS = 'claims';
|
|
5
|
+
const DEFAULT_REFRESH_TTL_SEC = 7 * 24 * 60 * 60;
|
|
6
|
+
export class SessionClient {
|
|
7
|
+
constructor(init) {
|
|
8
|
+
this.init = init;
|
|
9
|
+
}
|
|
10
|
+
k(sessionId, suffix) {
|
|
11
|
+
return `${sessionId}:${suffix}`;
|
|
12
|
+
}
|
|
13
|
+
async getAll(sessionId) {
|
|
14
|
+
const claims = await this.init.store.get(this.k(sessionId, CLAIMS));
|
|
15
|
+
const access_token = await this.init.store.get(this.k(sessionId, ACCESS));
|
|
16
|
+
const id_token = await this.init.store.get(this.k(sessionId, ID));
|
|
17
|
+
const refresh_token = await this.init.store.get(this.k(sessionId, REFRESH));
|
|
18
|
+
return { claims, access_token, id_token, refresh_token };
|
|
19
|
+
}
|
|
20
|
+
async setTokens(sessionId, t) {
|
|
21
|
+
const { store } = this.init;
|
|
22
|
+
await store.set(this.k(sessionId, ACCESS), t.access_token, t.ttlSec);
|
|
23
|
+
await store.set(this.k(sessionId, ID), t.id_token, t.ttlSec);
|
|
24
|
+
if (t.refresh_token) {
|
|
25
|
+
const refreshTtl = this.init.refresh?.defaultTtlSec ?? DEFAULT_REFRESH_TTL_SEC;
|
|
26
|
+
await store.set(this.k(sessionId, REFRESH), t.refresh_token, refreshTtl);
|
|
27
|
+
}
|
|
28
|
+
// No JWT verification. Clear cached claims to force re-derive later (future step).
|
|
29
|
+
await store.del(this.k(sessionId, CLAIMS));
|
|
30
|
+
}
|
|
31
|
+
async getTtl(_sessionId) {
|
|
32
|
+
// Will compute from JWT exp later; parity with original means null for now.
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
async clear(sessionId) {
|
|
36
|
+
await Promise.all([
|
|
37
|
+
this.init.store.del(this.k(sessionId, ACCESS)),
|
|
38
|
+
this.init.store.del(this.k(sessionId, ID)),
|
|
39
|
+
this.init.store.del(this.k(sessionId, REFRESH)),
|
|
40
|
+
this.init.store.del(this.k(sessionId, CLAIMS)),
|
|
41
|
+
]);
|
|
42
|
+
}
|
|
43
|
+
async maybeRefresh(_sessionId) {
|
|
44
|
+
// Stub; parity with original (no auto-refresh yet).
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SessionStore } from '../types';
|
|
2
|
+
export declare class MemoryStore implements SessionStore {
|
|
3
|
+
private map;
|
|
4
|
+
get<T = unknown>(key: string): Promise<T | null>;
|
|
5
|
+
set<T = unknown>(key: string, value: T, ttlSec?: number): Promise<void>;
|
|
6
|
+
del(key: string): Promise<void>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export class MemoryStore {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.map = new Map();
|
|
4
|
+
}
|
|
5
|
+
async get(key) {
|
|
6
|
+
const now = Date.now();
|
|
7
|
+
const hit = this.map.get(key);
|
|
8
|
+
if (!hit)
|
|
9
|
+
return null;
|
|
10
|
+
if (hit.expiresAt !== undefined && hit.expiresAt <= now) {
|
|
11
|
+
this.map.delete(key);
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
return hit.value;
|
|
15
|
+
}
|
|
16
|
+
async set(key, value, ttlSec) {
|
|
17
|
+
const expiresAt = ttlSec !== undefined && ttlSec > 0 ? Date.now() + ttlSec * 1000 : undefined;
|
|
18
|
+
this.map.set(key, { value, expiresAt });
|
|
19
|
+
}
|
|
20
|
+
async del(key) {
|
|
21
|
+
this.map.delete(key);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { MemoryStore } from './MemoryStore';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { MemoryStore } from './MemoryStore';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface SessionStore {
|
|
2
|
+
get<T = unknown>(key: string): Promise<T | null>;
|
|
3
|
+
set<T = unknown>(key: string, value: T, ttlSec?: number): Promise<void>;
|
|
4
|
+
del(key: string): Promise<void>;
|
|
5
|
+
}
|
|
6
|
+
export type SameSite = 'lax' | 'strict' | 'none';
|
|
7
|
+
export interface JWKSOptions {
|
|
8
|
+
issuer: string;
|
|
9
|
+
jwksUri: string;
|
|
10
|
+
cacheTtlSec?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface RequiredClaims {
|
|
13
|
+
sub: string;
|
|
14
|
+
organization_id?: number | string;
|
|
15
|
+
role?: string;
|
|
16
|
+
[k: string]: unknown;
|
|
17
|
+
}
|
|
18
|
+
export interface SessionClientInit {
|
|
19
|
+
store: SessionStore;
|
|
20
|
+
refresh?: {
|
|
21
|
+
endpoint: string;
|
|
22
|
+
graceSec?: number;
|
|
23
|
+
defaultTtlSec?: number;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export interface TokenBundle {
|
|
27
|
+
id_token: string;
|
|
28
|
+
access_token: string;
|
|
29
|
+
refresh_token?: string;
|
|
30
|
+
ttlSec?: number;
|
|
31
|
+
}
|
|
32
|
+
export interface SessionSnapshot {
|
|
33
|
+
claims: unknown | null;
|
|
34
|
+
access_token: string | null;
|
|
35
|
+
id_token: string | null;
|
|
36
|
+
refresh_token: string | null;
|
|
37
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { AuthConfig } from './auth.server';
|
|
2
|
+
/**
|
|
3
|
+
* SSI-related config that host apps typically derive from the current request
|
|
4
|
+
* (e.g. via getRequestConfig(req)) and pass to the library.
|
|
5
|
+
*
|
|
6
|
+
* Use this type when building a "request config" in your app so SessionManager,
|
|
7
|
+
* AuthServer, UsageApi, and SubscribedPlanManager get a consistent, typed shape.
|
|
8
|
+
*
|
|
9
|
+
* - **ssiIssuerBaseUrl**, **ssiRedirectUri**, **ssiClientId**: passed to
|
|
10
|
+
* AuthServer and SessionManager.fromEnv(cookieHeader, config).
|
|
11
|
+
* - **ssiApiUrl**: base URL for SSI Platform API (UsageApi, SubscribedPlanManager,
|
|
12
|
+
* SSIProjectApi, etc.).
|
|
13
|
+
* - **baseUrl** / **apiUrl**: optional; for app use (e.g. redirect after login).
|
|
14
|
+
* The library does not use these; they are included so apps can use this type
|
|
15
|
+
* as the SSI subset of a larger RequestConfig.
|
|
16
|
+
*/
|
|
17
|
+
export type SSIRequestConfig = {
|
|
18
|
+
baseUrl?: string | null;
|
|
19
|
+
apiUrl?: string | null;
|
|
20
|
+
ssiApiUrl: string | null;
|
|
21
|
+
ssiIssuerBaseUrl: string | null;
|
|
22
|
+
ssiRedirectUri: string | null;
|
|
23
|
+
ssiClientId: string | null;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Get the base URL from request headers or environment.
|
|
27
|
+
* Uses x-forwarded-proto, x-forwarded-host, origin, host, and NEXT_PUBLIC_APP_URL.
|
|
28
|
+
*/
|
|
29
|
+
export declare function getBaseUrl(req: Request): string | null;
|
|
30
|
+
/** When baseUrl is a single URL or comma-separated list, return the first URL. */
|
|
31
|
+
export declare function firstBaseUrl(baseUrl: string | null): string | null;
|
|
32
|
+
/** App API base URL: NEXT_PUBLIC_API_URL or baseUrl. */
|
|
33
|
+
export declare function getBaseApiUrl(req: Request): string | null;
|
|
34
|
+
/** SSI client id: SSI_CLIENT_ID env or subdomain on known Cereal Stack domains. */
|
|
35
|
+
export declare function getSsiClientId(baseUrl: string): string | null;
|
|
36
|
+
/** SSI issuer base URL: SSI_ISSUER_BASE_URL env or derived from host (getSsiApiUrl). */
|
|
37
|
+
export declare function getIssuerBaseUrl(baseUrl: string): string | null;
|
|
38
|
+
/**
|
|
39
|
+
* SSI Platform API base URL: SSI_API_BASE_URL env or derived from host
|
|
40
|
+
* (e.g. https://<subdomain>.cerealstackdev.com on known domains).
|
|
41
|
+
*/
|
|
42
|
+
export declare function getSsiApiUrl(baseUrl: string): string | null;
|
|
43
|
+
/** SSI redirect URI: SSI_REDIRECT_URI env or `${baseUrl}/api/v1/auth/callback`. */
|
|
44
|
+
export declare function getSsiRedirectUri(baseUrl: string): string | null;
|
|
45
|
+
export declare function getRequestConfig(req: Request): SSIRequestConfig;
|
|
46
|
+
export declare function getRequestConfig(headers: Headers): SSIRequestConfig;
|
|
47
|
+
/**
|
|
48
|
+
* Union type accepted anywhere AuthConfig is accepted (AuthServer, SessionManager).
|
|
49
|
+
*/
|
|
50
|
+
export type AuthConfigInput = AuthConfig | SSIRequestConfig;
|
|
51
|
+
/**
|
|
52
|
+
* Converts SSIRequestConfig to AuthConfig for use with AuthServer and SessionManager.
|
|
53
|
+
* Drops null/undefined so the library can fall back to env vars where appropriate.
|
|
54
|
+
*/
|
|
55
|
+
export declare function toAuthConfig(rc: SSIRequestConfig): AuthConfig;
|
|
56
|
+
/**
|
|
57
|
+
* Normalizes AuthConfigInput to AuthConfig. Use internally so AuthServer and SessionManager
|
|
58
|
+
* accept both AuthConfig and SSIRequestConfig.
|
|
59
|
+
*/
|
|
60
|
+
export declare function normalizeAuthConfig(input: AuthConfigInput | undefined): AuthConfig | undefined;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// requestConfig.ts
|
|
3
|
+
// Types and helpers for config that host apps derive per-request and pass into AuthServer, SessionManager, and SSI API clients.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.getBaseUrl = getBaseUrl;
|
|
6
|
+
exports.firstBaseUrl = firstBaseUrl;
|
|
7
|
+
exports.getBaseApiUrl = getBaseApiUrl;
|
|
8
|
+
exports.getSsiClientId = getSsiClientId;
|
|
9
|
+
exports.getIssuerBaseUrl = getIssuerBaseUrl;
|
|
10
|
+
exports.getSsiApiUrl = getSsiApiUrl;
|
|
11
|
+
exports.getSsiRedirectUri = getSsiRedirectUri;
|
|
12
|
+
exports.getRequestConfig = getRequestConfig;
|
|
13
|
+
exports.toAuthConfig = toAuthConfig;
|
|
14
|
+
exports.normalizeAuthConfig = normalizeAuthConfig;
|
|
15
|
+
// --- Helpers used by getRequestConfig (env: NEXT_PUBLIC_APP_URL, SSI_*, request headers) ---
|
|
16
|
+
/**
|
|
17
|
+
* Get the base URL from request headers or environment.
|
|
18
|
+
* Uses x-forwarded-proto, x-forwarded-host, origin, host, and NEXT_PUBLIC_APP_URL.
|
|
19
|
+
*/
|
|
20
|
+
function getBaseUrl(req) {
|
|
21
|
+
const proto = req.headers.get('x-forwarded-proto') || 'https';
|
|
22
|
+
let baseUrl = process.env.NEXT_PUBLIC_APP_URL;
|
|
23
|
+
if (!baseUrl) {
|
|
24
|
+
const forwardedHost = req.headers.get('x-forwarded-host');
|
|
25
|
+
if (forwardedHost) {
|
|
26
|
+
baseUrl = `${proto}://${forwardedHost}`;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
const origin = req.headers.get('origin');
|
|
30
|
+
if (origin) {
|
|
31
|
+
baseUrl = origin;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
const host = req.headers.get('host');
|
|
35
|
+
const hostWithoutPort = host ? host.split(':')[0] : undefined;
|
|
36
|
+
if (hostWithoutPort) {
|
|
37
|
+
baseUrl = `${proto}://${hostWithoutPort}`;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return baseUrl || null;
|
|
43
|
+
}
|
|
44
|
+
/** When baseUrl is a single URL or comma-separated list, return the first URL. */
|
|
45
|
+
function firstBaseUrl(baseUrl) {
|
|
46
|
+
if (!baseUrl)
|
|
47
|
+
return null;
|
|
48
|
+
const first = baseUrl.split(',')[0].trim();
|
|
49
|
+
return first || null;
|
|
50
|
+
}
|
|
51
|
+
/** App API base URL: NEXT_PUBLIC_API_URL or baseUrl. */
|
|
52
|
+
function getBaseApiUrl(req) {
|
|
53
|
+
return process.env.NEXT_PUBLIC_API_URL || getBaseUrl(req) || null;
|
|
54
|
+
}
|
|
55
|
+
/** SSI client id: SSI_CLIENT_ID env or subdomain on known Cereal Stack domains. */
|
|
56
|
+
function getSsiClientId(baseUrl) {
|
|
57
|
+
if (process.env.SSI_CLIENT_ID)
|
|
58
|
+
return process.env.SSI_CLIENT_ID;
|
|
59
|
+
try {
|
|
60
|
+
const url = new URL(baseUrl);
|
|
61
|
+
url.pathname = url.pathname.replace(/\/$/, '');
|
|
62
|
+
const hostname = url.hostname;
|
|
63
|
+
if (hostname.endsWith('.cerealstackdev.com') || hostname.endsWith('.cerealstackqa.com') || hostname.endsWith('.cerealstack.com')) {
|
|
64
|
+
const domainParts = hostname.split('.');
|
|
65
|
+
const subdomain = domainParts[domainParts.length - 3];
|
|
66
|
+
return subdomain ?? null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// ignore
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
/** SSI issuer base URL: SSI_ISSUER_BASE_URL env or derived from host (getSsiApiUrl). */
|
|
75
|
+
function getIssuerBaseUrl(baseUrl) {
|
|
76
|
+
return process.env.SSI_ISSUER_BASE_URL || getSsiApiUrl(baseUrl) || null;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* SSI Platform API base URL: SSI_API_BASE_URL env or derived from host
|
|
80
|
+
* (e.g. https://<subdomain>.cerealstackdev.com on known domains).
|
|
81
|
+
*/
|
|
82
|
+
function getSsiApiUrl(baseUrl) {
|
|
83
|
+
if (process.env.SSI_API_BASE_URL)
|
|
84
|
+
return process.env.SSI_API_BASE_URL;
|
|
85
|
+
try {
|
|
86
|
+
const url = new URL(baseUrl);
|
|
87
|
+
url.pathname = url.pathname.replace(/\/$/, '');
|
|
88
|
+
const hostname = url.hostname;
|
|
89
|
+
if (hostname.endsWith('.cerealstackdev.com') || hostname.endsWith('.cerealstackqa.com') || hostname.endsWith('.cerealstack.com')) {
|
|
90
|
+
const domainParts = hostname.split('.');
|
|
91
|
+
const subdomain = domainParts[domainParts.length - 3];
|
|
92
|
+
const domain = domainParts[domainParts.length - 2] + '.' + domainParts[domainParts.length - 1];
|
|
93
|
+
return `https://${subdomain}.${domain}`;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// ignore
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
/** SSI redirect URI: SSI_REDIRECT_URI env or `${baseUrl}/api/v1/auth/callback`. */
|
|
102
|
+
function getSsiRedirectUri(baseUrl) {
|
|
103
|
+
return process.env.SSI_REDIRECT_URI || `${baseUrl}/api/v1/auth/callback`;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Build SSIRequestConfig from the current request (or headers).
|
|
107
|
+
* Uses env vars (NEXT_PUBLIC_APP_URL, SSI_*, etc.) and request headers for base URL and SSI URLs.
|
|
108
|
+
* Apps can use this as-is or extend the result with app-specific fields (e.g. prisma, dbUrl).
|
|
109
|
+
*/
|
|
110
|
+
function getRequestConfig(arg) {
|
|
111
|
+
const req = arg instanceof Headers ? new Request('http://local', { headers: arg }) : arg;
|
|
112
|
+
const rawBaseUrl = getBaseUrl(req);
|
|
113
|
+
const baseUrl = firstBaseUrl(rawBaseUrl);
|
|
114
|
+
const fallback = baseUrl || '';
|
|
115
|
+
return {
|
|
116
|
+
baseUrl: baseUrl ?? null,
|
|
117
|
+
apiUrl: getBaseApiUrl(req),
|
|
118
|
+
ssiApiUrl: getSsiApiUrl(fallback),
|
|
119
|
+
ssiIssuerBaseUrl: getIssuerBaseUrl(fallback),
|
|
120
|
+
ssiRedirectUri: getSsiRedirectUri(fallback),
|
|
121
|
+
ssiClientId: getSsiClientId(fallback),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function isSSIRequestConfig(x) {
|
|
125
|
+
return x != null && typeof x === 'object' && 'ssiIssuerBaseUrl' in x;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Converts SSIRequestConfig to AuthConfig for use with AuthServer and SessionManager.
|
|
129
|
+
* Drops null/undefined so the library can fall back to env vars where appropriate.
|
|
130
|
+
*/
|
|
131
|
+
function toAuthConfig(rc) {
|
|
132
|
+
const auth = {};
|
|
133
|
+
if (rc.ssiIssuerBaseUrl != null && rc.ssiIssuerBaseUrl !== '')
|
|
134
|
+
auth.issuerBaseUrl = rc.ssiIssuerBaseUrl;
|
|
135
|
+
if (rc.ssiRedirectUri != null && rc.ssiRedirectUri !== '')
|
|
136
|
+
auth.redirectUri = rc.ssiRedirectUri;
|
|
137
|
+
if (rc.ssiClientId != null && rc.ssiClientId !== '')
|
|
138
|
+
auth.clientId = rc.ssiClientId;
|
|
139
|
+
return auth;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Normalizes AuthConfigInput to AuthConfig. Use internally so AuthServer and SessionManager
|
|
143
|
+
* accept both AuthConfig and SSIRequestConfig.
|
|
144
|
+
*/
|
|
145
|
+
function normalizeAuthConfig(input) {
|
|
146
|
+
if (input == null)
|
|
147
|
+
return undefined;
|
|
148
|
+
if (isSSIRequestConfig(input))
|
|
149
|
+
return toAuthConfig(input);
|
|
150
|
+
return input;
|
|
151
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare class SessionClient {
|
|
2
|
+
private readonly cookieHeader?;
|
|
3
|
+
private static readonly instances;
|
|
4
|
+
private readonly baseUrl;
|
|
5
|
+
private claims;
|
|
6
|
+
private ttl;
|
|
7
|
+
private initPromise;
|
|
8
|
+
private static normalizeBaseUrl;
|
|
9
|
+
constructor(baseUrl?: string, cookieHeader?: string | null | undefined);
|
|
10
|
+
static getSessionManager(baseUrl?: string): SessionClient;
|
|
11
|
+
isLoggedIn(): Promise<boolean>;
|
|
12
|
+
warmup(): void;
|
|
13
|
+
getTtl(): Promise<number | null>;
|
|
14
|
+
get(claimName: string): Promise<unknown>;
|
|
15
|
+
getAll(): Promise<unknown | null>;
|
|
16
|
+
private withSession;
|
|
17
|
+
private loadClaims;
|
|
18
|
+
}
|
|
19
|
+
export default SessionClient;
|