@mitralab.io/platform-sdk 1.1.0-beta.2 → 1.1.0-beta.3
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 +24 -0
- package/dist/index.cjs +109 -26
- package/dist/index.d.cts +33 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +85 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -78,6 +78,30 @@ Authentication state is stored under `mitra_auth_{appId}`. Before each authentic
|
|
|
78
78
|
|
|
79
79
|
The generated-application authentication flow is Google SSO. The old native `signIn` and `signUp` names fail locally with `UNSUPPORTED_AUTH_METHOD` because IAM has no email/password endpoints. Deprecated login bindings remain available only through the legacy reexports.
|
|
80
80
|
|
|
81
|
+
### Signing in as a process
|
|
82
|
+
|
|
83
|
+
Google and Microsoft SSO both need a person to complete a redirect. A process that runs on its own -
|
|
84
|
+
a cron, a background collector, another service - signs in with an api key created in
|
|
85
|
+
**Settings -> API keys**:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
const mitra = createClient({ appId, apiUrl, apiKey: process.env.MITRA_API_KEY })
|
|
89
|
+
|
|
90
|
+
await mitra.auth.signInWithApiKey()
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Which key to create depends on what the process does. A **Business** key reaches the published
|
|
94
|
+
product: read and write records, run queries, execute functions and integrations. A **Developer**
|
|
95
|
+
key adds authoring. An **Administrator** key belongs to a workspace rather than a product; the SDK
|
|
96
|
+
issues this app's token from it, and the platform decides on that call what the key's owner
|
|
97
|
+
actually reaches here, so the key never widens anyone's access.
|
|
98
|
+
|
|
99
|
+
This session is not written to storage and carries no refresh token. The key does not expire and is
|
|
100
|
+
revoked by deleting it, so renewing means signing in again rather than holding a second secret.
|
|
101
|
+
|
|
102
|
+
**Server runtimes only.** In a browser the key would be served to every visitor inside the bundle,
|
|
103
|
+
so the method refuses to run when a `window` exists.
|
|
104
|
+
|
|
81
105
|
An embedded preview can adopt the app-scoped session it receives from the platform without exchanging it:
|
|
82
106
|
|
|
83
107
|
```typescript
|
package/dist/index.cjs
CHANGED
|
@@ -60,7 +60,7 @@ function stripTrailingSlashes(value) {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
// src/client.ts
|
|
63
|
-
var
|
|
63
|
+
var import_sdk_core10 = require("@mitralab.io/sdk-core");
|
|
64
64
|
|
|
65
65
|
// src/utils/http-client.ts
|
|
66
66
|
var bearerCredentialPattern = /(Bearer\s+)\S+/gi;
|
|
@@ -396,10 +396,46 @@ function adoptLegacySession(session) {
|
|
|
396
396
|
}
|
|
397
397
|
|
|
398
398
|
// src/modules/auth.ts
|
|
399
|
-
var
|
|
399
|
+
var import_sdk_core3 = require("@mitralab.io/sdk-core");
|
|
400
400
|
|
|
401
|
-
// src/modules/
|
|
401
|
+
// src/modules/api-key-auth.ts
|
|
402
402
|
var import_sdk_core = require("@mitralab.io/sdk-core");
|
|
403
|
+
async function withoutLeakingKey(apiKey, call) {
|
|
404
|
+
try {
|
|
405
|
+
return await call();
|
|
406
|
+
} catch (error) {
|
|
407
|
+
if (error instanceof MitraApiError && error.message.includes(apiKey)) {
|
|
408
|
+
throw new MitraApiError(error.message.split(apiKey).join("[REDACTED]"), error.status);
|
|
409
|
+
}
|
|
410
|
+
throw error;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
function assertServerRuntime() {
|
|
414
|
+
if (globalThis.window !== void 0) {
|
|
415
|
+
throw new MitraApiError(
|
|
416
|
+
"Api key authentication runs only on a server. In a browser the key would be served to every visitor; sign in with Google or Microsoft instead.",
|
|
417
|
+
400
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
async function resolveApiKeySession(publicClient, appId, apiKey) {
|
|
422
|
+
return (0, import_sdk_core.resolveApiKeyToken)(
|
|
423
|
+
(path, init) => withoutLeakingKey(
|
|
424
|
+
apiKey,
|
|
425
|
+
() => publicClient.request(path, {
|
|
426
|
+
method: "POST",
|
|
427
|
+
...init.body === void 0 ? {} : { body: init.body },
|
|
428
|
+
...init.bearer === void 0 ? {} : { headers: { Authorization: `Bearer ${init.bearer}` } }
|
|
429
|
+
})
|
|
430
|
+
),
|
|
431
|
+
appId,
|
|
432
|
+
apiKey,
|
|
433
|
+
coreErrors
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// src/modules/google-auth.ts
|
|
438
|
+
var import_sdk_core2 = require("@mitralab.io/sdk-core");
|
|
403
439
|
var RESULT_TYPE = "mitra-oauth-result";
|
|
404
440
|
var PROVIDER_LABELS = {
|
|
405
441
|
google: "Google",
|
|
@@ -410,7 +446,7 @@ var POPUP_HEIGHT = 600;
|
|
|
410
446
|
var POPUP_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
411
447
|
var POPUP_CLOSED_POLL_MS = 500;
|
|
412
448
|
function expectAuthTokenResponse(value) {
|
|
413
|
-
const response = (0,
|
|
449
|
+
const response = (0, import_sdk_core2.expectObject)(
|
|
414
450
|
value,
|
|
415
451
|
"Authentication token response",
|
|
416
452
|
coreErrors
|
|
@@ -685,6 +721,7 @@ function isDefinitiveRefreshFailure(error) {
|
|
|
685
721
|
}
|
|
686
722
|
var AuthModule = class {
|
|
687
723
|
appId;
|
|
724
|
+
defaultApiKey;
|
|
688
725
|
_currentUser = null;
|
|
689
726
|
#accessToken = null;
|
|
690
727
|
#refreshToken = null;
|
|
@@ -705,6 +742,7 @@ var AuthModule = class {
|
|
|
705
742
|
const apiUrl = stripTrailingSlashes(
|
|
706
743
|
options.apiUrl ?? (trimmedIamBaseUrl.endsWith("/iam") ? trimmedIamBaseUrl.slice(0, -"/iam".length) : trimmedIamBaseUrl)
|
|
707
744
|
);
|
|
745
|
+
this.defaultApiKey = options.apiKey;
|
|
708
746
|
this.storageKey = `mitra_auth_${appId}`;
|
|
709
747
|
this.publicClient = new HttpClient({ baseUrl: iamBaseUrl, getToken: () => null });
|
|
710
748
|
this.authedClient = new HttpClient({
|
|
@@ -713,7 +751,7 @@ var AuthModule = class {
|
|
|
713
751
|
beforeAuthenticatedRequest: () => this.ensureFreshSession().then(() => void 0),
|
|
714
752
|
onUnauthorized: (requestToken) => this.handleUnauthorized(requestToken)
|
|
715
753
|
});
|
|
716
|
-
this.currentUserApi = (0,
|
|
754
|
+
this.currentUserApi = (0, import_sdk_core3.createAuthModule)(this.authedClient, coreErrors);
|
|
717
755
|
this.googleAuth = new GoogleAuthFlow({
|
|
718
756
|
appId,
|
|
719
757
|
apiUrl,
|
|
@@ -791,6 +829,51 @@ var AuthModule = class {
|
|
|
791
829
|
async signInWithGoogle(options = {}) {
|
|
792
830
|
return this.establishSession(await this.googleAuth.signIn(options));
|
|
793
831
|
}
|
|
832
|
+
/**
|
|
833
|
+
* Signs in as a process, with an api key instead of a person's SSO session.
|
|
834
|
+
*
|
|
835
|
+
* Use this where nobody can complete a redirect: a cron, a background collector, another
|
|
836
|
+
* service. Create the key in Settings -> API keys. A Business key reaches the published
|
|
837
|
+
* product, a Developer key adds authoring, and an Administrator key belongs to a workspace -
|
|
838
|
+
* from that one the platform issues this app's token, deciding then and there what the key's
|
|
839
|
+
* owner actually reaches here.
|
|
840
|
+
*
|
|
841
|
+
* The session is not written to storage and carries no refresh token: the key does not
|
|
842
|
+
* expire and is revoked by deleting it, so renewing means signing in again.
|
|
843
|
+
*
|
|
844
|
+
* Server runtimes only. In a browser the key would ship inside the bundle, so this method
|
|
845
|
+
* refuses to run there.
|
|
846
|
+
*
|
|
847
|
+
* @param apiKey - The key to use. Defaults to `apiKey` from the client configuration.
|
|
848
|
+
* @returns The user the key belongs to.
|
|
849
|
+
*
|
|
850
|
+
* @example
|
|
851
|
+
* ```typescript
|
|
852
|
+
* const mitra = createClient({ appId, apiUrl, apiKey: process.env.MITRA_API_KEY });
|
|
853
|
+
* await mitra.auth.signInWithApiKey();
|
|
854
|
+
* ```
|
|
855
|
+
*/
|
|
856
|
+
async signInWithApiKey(apiKey = this.defaultApiKey) {
|
|
857
|
+
assertServerRuntime();
|
|
858
|
+
if (!apiKey?.trim()) {
|
|
859
|
+
throw new MitraApiError(
|
|
860
|
+
"No api key was given. Pass one to signInWithApiKey, or set apiKey when creating the client.",
|
|
861
|
+
400
|
|
862
|
+
);
|
|
863
|
+
}
|
|
864
|
+
const token = await resolveApiKeySession(this.publicClient, this.appId, apiKey);
|
|
865
|
+
if (!this.belongsToConfiguredApp(token)) {
|
|
866
|
+
throw new MitraApiError("This api key does not reach the app this client is configured for.", 403);
|
|
867
|
+
}
|
|
868
|
+
this.invalidatePendingRefreshes();
|
|
869
|
+
this.#accessToken = token;
|
|
870
|
+
this.#refreshToken = null;
|
|
871
|
+
const user = await this.getCurrentUser();
|
|
872
|
+
this._currentUser = user;
|
|
873
|
+
this.notifySessionListeners();
|
|
874
|
+
this.notifyListeners();
|
|
875
|
+
return user;
|
|
876
|
+
}
|
|
794
877
|
/**
|
|
795
878
|
* Completes a Google redirect response from `#codeMitra` and `#stateMitra`.
|
|
796
879
|
*
|
|
@@ -1270,11 +1353,11 @@ var AuthModule = class {
|
|
|
1270
1353
|
};
|
|
1271
1354
|
|
|
1272
1355
|
// src/modules/entities.ts
|
|
1273
|
-
var
|
|
1356
|
+
var import_sdk_core4 = require("@mitralab.io/sdk-core");
|
|
1274
1357
|
var EntitiesModule = class _EntitiesModule {
|
|
1275
1358
|
constructor(httpClient, _dataSourceId) {
|
|
1276
1359
|
this.httpClient = httpClient;
|
|
1277
|
-
this.core = (0,
|
|
1360
|
+
this.core = (0, import_sdk_core4.createEntitiesModule)(httpClient, coreErrors);
|
|
1278
1361
|
}
|
|
1279
1362
|
core;
|
|
1280
1363
|
static createProxy(httpClient, dataSourceId) {
|
|
@@ -1293,7 +1376,7 @@ var EntitiesModule = class _EntitiesModule {
|
|
|
1293
1376
|
* Records now resolve the app from authenticated context instead of a data source path.
|
|
1294
1377
|
*/
|
|
1295
1378
|
setDataSourceId(_dataSourceId) {
|
|
1296
|
-
this.core = (0,
|
|
1379
|
+
this.core = (0, import_sdk_core4.createEntitiesModule)(this.httpClient, coreErrors);
|
|
1297
1380
|
}
|
|
1298
1381
|
getTable(tableName) {
|
|
1299
1382
|
return this.core.getTable(tableName);
|
|
@@ -1301,11 +1384,11 @@ var EntitiesModule = class _EntitiesModule {
|
|
|
1301
1384
|
};
|
|
1302
1385
|
|
|
1303
1386
|
// src/modules/functions.ts
|
|
1304
|
-
var
|
|
1387
|
+
var import_sdk_core5 = require("@mitralab.io/sdk-core");
|
|
1305
1388
|
var FunctionsModule = class {
|
|
1306
1389
|
core;
|
|
1307
1390
|
constructor(httpClient) {
|
|
1308
|
-
this.core = (0,
|
|
1391
|
+
this.core = (0, import_sdk_core5.createFunctionsModule)(
|
|
1309
1392
|
httpClient,
|
|
1310
1393
|
{ emptyInput: "omit-body", executeInvocationType: "sync" },
|
|
1311
1394
|
coreErrors
|
|
@@ -1332,13 +1415,13 @@ var FunctionsModule = class {
|
|
|
1332
1415
|
};
|
|
1333
1416
|
|
|
1334
1417
|
// src/modules/integration.ts
|
|
1335
|
-
var
|
|
1418
|
+
var import_sdk_core6 = require("@mitralab.io/sdk-core");
|
|
1336
1419
|
var IntegrationModule = class {
|
|
1337
1420
|
core;
|
|
1338
1421
|
configs;
|
|
1339
1422
|
constructor(httpClient) {
|
|
1340
|
-
this.core = (0,
|
|
1341
|
-
this.configs = (0,
|
|
1423
|
+
this.core = (0, import_sdk_core6.createIntegrationModule)(httpClient, coreErrors);
|
|
1424
|
+
this.configs = (0, import_sdk_core6.createIntegrationAdminModule)(httpClient, coreErrors);
|
|
1342
1425
|
}
|
|
1343
1426
|
/** Lists the current app's integration configs without exposing admin mutations. */
|
|
1344
1427
|
list(options) {
|
|
@@ -1357,11 +1440,11 @@ var IntegrationModule = class {
|
|
|
1357
1440
|
};
|
|
1358
1441
|
|
|
1359
1442
|
// src/modules/queries.ts
|
|
1360
|
-
var
|
|
1443
|
+
var import_sdk_core7 = require("@mitralab.io/sdk-core");
|
|
1361
1444
|
var QueriesModule = class {
|
|
1362
1445
|
core;
|
|
1363
1446
|
constructor(httpClient) {
|
|
1364
|
-
this.core = (0,
|
|
1447
|
+
this.core = (0, import_sdk_core7.createQueriesModule)(httpClient, coreErrors);
|
|
1365
1448
|
}
|
|
1366
1449
|
/**
|
|
1367
1450
|
* @deprecated Preserved for Platform SDK 1.x source compatibility. Data Manager now resolves
|
|
@@ -1375,7 +1458,7 @@ var QueriesModule = class {
|
|
|
1375
1458
|
};
|
|
1376
1459
|
|
|
1377
1460
|
// src/modules/agent-tasks.ts
|
|
1378
|
-
var
|
|
1461
|
+
var import_sdk_core8 = require("@mitralab.io/sdk-core");
|
|
1379
1462
|
|
|
1380
1463
|
// src/modules/agent-session.ts
|
|
1381
1464
|
var CONNECT_TIMEOUT_MS = 15e3;
|
|
@@ -1588,23 +1671,23 @@ var BrowserAgentTaskEventSource = class {
|
|
|
1588
1671
|
|
|
1589
1672
|
// src/modules/agent-tasks.ts
|
|
1590
1673
|
function createBrowserAgentTasksModule(httpClient, auth, apiUrl) {
|
|
1591
|
-
const tasks = (0,
|
|
1592
|
-
const manager = (0,
|
|
1674
|
+
const tasks = (0, import_sdk_core8.createAgentTasksModule)(httpClient, coreErrors);
|
|
1675
|
+
const manager = (0, import_sdk_core8.createAgentTaskSessionManager)({
|
|
1593
1676
|
tasks,
|
|
1594
1677
|
eventSource: new BrowserAgentTaskEventSource(auth, apiUrl)
|
|
1595
1678
|
});
|
|
1596
|
-
return (0,
|
|
1679
|
+
return (0, import_sdk_core8.withAgentTaskSessions)(tasks, manager);
|
|
1597
1680
|
}
|
|
1598
1681
|
|
|
1599
1682
|
// src/modules/agent-credentials.ts
|
|
1600
|
-
var
|
|
1683
|
+
var import_sdk_core9 = require("@mitralab.io/sdk-core");
|
|
1601
1684
|
function requireProvider(provider, allowed, flow) {
|
|
1602
1685
|
if (!allowed.includes(provider)) {
|
|
1603
1686
|
throw new TypeError(`${flow} does not support provider ${provider}.`);
|
|
1604
1687
|
}
|
|
1605
1688
|
}
|
|
1606
1689
|
function createBrowserAgentCredentialsModule(httpClient) {
|
|
1607
|
-
const core = (0,
|
|
1690
|
+
const core = (0, import_sdk_core9.createAgentCredentialsModule)(httpClient, coreErrors);
|
|
1608
1691
|
return {
|
|
1609
1692
|
list: () => core.list(),
|
|
1610
1693
|
listModels: (agentId) => core.listModels(agentId),
|
|
@@ -1637,7 +1720,7 @@ function createBrowserAgentCredentialsModule(httpClient) {
|
|
|
1637
1720
|
|
|
1638
1721
|
// src/client.ts
|
|
1639
1722
|
function expectAppInfoResponse(value) {
|
|
1640
|
-
const response = (0,
|
|
1723
|
+
const response = (0, import_sdk_core10.expectObject)(
|
|
1641
1724
|
value,
|
|
1642
1725
|
"App info response",
|
|
1643
1726
|
coreErrors
|
|
@@ -1658,7 +1741,7 @@ function expectAppInfoResponse(value) {
|
|
|
1658
1741
|
};
|
|
1659
1742
|
}
|
|
1660
1743
|
function createClient(config) {
|
|
1661
|
-
const { appId, apiUrl, authPageUrl, onError } = config;
|
|
1744
|
+
const { appId, apiUrl, apiKey, authPageUrl, onError } = config;
|
|
1662
1745
|
const gatewayUrl = stripTrailingSlashes(apiUrl);
|
|
1663
1746
|
const iamUrl = `${gatewayUrl}/iam`;
|
|
1664
1747
|
const dataManagerUrl = `${gatewayUrl}/data-manager`;
|
|
@@ -1666,7 +1749,7 @@ function createClient(config) {
|
|
|
1666
1749
|
const integrationUrl = `${gatewayUrl}/integration`;
|
|
1667
1750
|
const codeStudioUrl = `${gatewayUrl}/code-studio`;
|
|
1668
1751
|
const copilotUrl = `${gatewayUrl}/copilot`;
|
|
1669
|
-
const authModule = new AuthModule(appId, iamUrl, { apiUrl: gatewayUrl, authPageUrl });
|
|
1752
|
+
const authModule = new AuthModule(appId, iamUrl, { apiUrl: gatewayUrl, authPageUrl, apiKey });
|
|
1670
1753
|
const authSession = getAuthSessionPort(authModule);
|
|
1671
1754
|
const onUnauthorized = (requestToken) => authSession.handleUnauthorized(requestToken);
|
|
1672
1755
|
const beforeAuthenticatedRequest = () => authModule.ensureFreshSession().then(() => void 0);
|
|
@@ -1689,7 +1772,7 @@ function createClient(config) {
|
|
|
1689
1772
|
defaultHeaders
|
|
1690
1773
|
});
|
|
1691
1774
|
const functionsModule = new FunctionsModule(functionsHttpClient);
|
|
1692
|
-
const publicFunctionsModule = (0,
|
|
1775
|
+
const publicFunctionsModule = (0, import_sdk_core10.createPublicFunctionsModule)(
|
|
1693
1776
|
new HttpClient({ baseUrl: functionsUrl, getToken: () => null }),
|
|
1694
1777
|
coreErrors
|
|
1695
1778
|
);
|
|
@@ -1730,7 +1813,7 @@ function createClient(config) {
|
|
|
1730
1813
|
});
|
|
1731
1814
|
const appInfo = expectAppInfoResponse(
|
|
1732
1815
|
await publicClient.get(
|
|
1733
|
-
`/api/v1/apps/${(0,
|
|
1816
|
+
`/api/v1/apps/${(0, import_sdk_core10.encodePathSegment)(appId, "appId", coreErrors)}/info`
|
|
1734
1817
|
)
|
|
1735
1818
|
);
|
|
1736
1819
|
if (appInfo.dataSourceId) {
|
package/dist/index.d.cts
CHANGED
|
@@ -216,6 +216,8 @@ type AuthStateChangeCallback = (user: User | null) => void;
|
|
|
216
216
|
interface AuthModuleOptions {
|
|
217
217
|
apiUrl?: string;
|
|
218
218
|
authPageUrl?: string;
|
|
219
|
+
/** Default api key for `signInWithApiKey`, taken from the client configuration. */
|
|
220
|
+
apiKey?: string;
|
|
219
221
|
}
|
|
220
222
|
/**
|
|
221
223
|
* Authentication module for managing user sessions.
|
|
@@ -233,6 +235,7 @@ interface AuthModuleOptions {
|
|
|
233
235
|
declare class AuthModule {
|
|
234
236
|
#private;
|
|
235
237
|
private readonly appId;
|
|
238
|
+
private readonly defaultApiKey;
|
|
236
239
|
private _currentUser;
|
|
237
240
|
private sessionGeneration;
|
|
238
241
|
private refreshFlight;
|
|
@@ -283,6 +286,31 @@ declare class AuthModule {
|
|
|
283
286
|
* ```
|
|
284
287
|
*/
|
|
285
288
|
signInWithGoogle(options?: GoogleSignInOptions): Promise<User>;
|
|
289
|
+
/**
|
|
290
|
+
* Signs in as a process, with an api key instead of a person's SSO session.
|
|
291
|
+
*
|
|
292
|
+
* Use this where nobody can complete a redirect: a cron, a background collector, another
|
|
293
|
+
* service. Create the key in Settings -> API keys. A Business key reaches the published
|
|
294
|
+
* product, a Developer key adds authoring, and an Administrator key belongs to a workspace -
|
|
295
|
+
* from that one the platform issues this app's token, deciding then and there what the key's
|
|
296
|
+
* owner actually reaches here.
|
|
297
|
+
*
|
|
298
|
+
* The session is not written to storage and carries no refresh token: the key does not
|
|
299
|
+
* expire and is revoked by deleting it, so renewing means signing in again.
|
|
300
|
+
*
|
|
301
|
+
* Server runtimes only. In a browser the key would ship inside the bundle, so this method
|
|
302
|
+
* refuses to run there.
|
|
303
|
+
*
|
|
304
|
+
* @param apiKey - The key to use. Defaults to `apiKey` from the client configuration.
|
|
305
|
+
* @returns The user the key belongs to.
|
|
306
|
+
*
|
|
307
|
+
* @example
|
|
308
|
+
* ```typescript
|
|
309
|
+
* const mitra = createClient({ appId, apiUrl, apiKey: process.env.MITRA_API_KEY });
|
|
310
|
+
* await mitra.auth.signInWithApiKey();
|
|
311
|
+
* ```
|
|
312
|
+
*/
|
|
313
|
+
signInWithApiKey(apiKey?: string | undefined): Promise<User>;
|
|
286
314
|
/**
|
|
287
315
|
* Completes a Google redirect response from `#codeMitra` and `#stateMitra`.
|
|
288
316
|
*
|
|
@@ -593,6 +621,11 @@ interface MitraClientConfig {
|
|
|
593
621
|
* Found in the Mitra Code Studio dashboard.
|
|
594
622
|
*/
|
|
595
623
|
appId: string;
|
|
624
|
+
/**
|
|
625
|
+
* Api key for server-side authentication, used by `auth.signInWithApiKey()`.
|
|
626
|
+
* Never set this in code that reaches a browser: it would ship in the bundle.
|
|
627
|
+
*/
|
|
628
|
+
apiKey?: string;
|
|
596
629
|
/**
|
|
597
630
|
* Base URL for the Mitra API (Kong Gateway).
|
|
598
631
|
* Injected automatically via `VITE_MITRA_API_URL` environment variable
|
package/dist/index.d.ts
CHANGED
|
@@ -216,6 +216,8 @@ type AuthStateChangeCallback = (user: User | null) => void;
|
|
|
216
216
|
interface AuthModuleOptions {
|
|
217
217
|
apiUrl?: string;
|
|
218
218
|
authPageUrl?: string;
|
|
219
|
+
/** Default api key for `signInWithApiKey`, taken from the client configuration. */
|
|
220
|
+
apiKey?: string;
|
|
219
221
|
}
|
|
220
222
|
/**
|
|
221
223
|
* Authentication module for managing user sessions.
|
|
@@ -233,6 +235,7 @@ interface AuthModuleOptions {
|
|
|
233
235
|
declare class AuthModule {
|
|
234
236
|
#private;
|
|
235
237
|
private readonly appId;
|
|
238
|
+
private readonly defaultApiKey;
|
|
236
239
|
private _currentUser;
|
|
237
240
|
private sessionGeneration;
|
|
238
241
|
private refreshFlight;
|
|
@@ -283,6 +286,31 @@ declare class AuthModule {
|
|
|
283
286
|
* ```
|
|
284
287
|
*/
|
|
285
288
|
signInWithGoogle(options?: GoogleSignInOptions): Promise<User>;
|
|
289
|
+
/**
|
|
290
|
+
* Signs in as a process, with an api key instead of a person's SSO session.
|
|
291
|
+
*
|
|
292
|
+
* Use this where nobody can complete a redirect: a cron, a background collector, another
|
|
293
|
+
* service. Create the key in Settings -> API keys. A Business key reaches the published
|
|
294
|
+
* product, a Developer key adds authoring, and an Administrator key belongs to a workspace -
|
|
295
|
+
* from that one the platform issues this app's token, deciding then and there what the key's
|
|
296
|
+
* owner actually reaches here.
|
|
297
|
+
*
|
|
298
|
+
* The session is not written to storage and carries no refresh token: the key does not
|
|
299
|
+
* expire and is revoked by deleting it, so renewing means signing in again.
|
|
300
|
+
*
|
|
301
|
+
* Server runtimes only. In a browser the key would ship inside the bundle, so this method
|
|
302
|
+
* refuses to run there.
|
|
303
|
+
*
|
|
304
|
+
* @param apiKey - The key to use. Defaults to `apiKey` from the client configuration.
|
|
305
|
+
* @returns The user the key belongs to.
|
|
306
|
+
*
|
|
307
|
+
* @example
|
|
308
|
+
* ```typescript
|
|
309
|
+
* const mitra = createClient({ appId, apiUrl, apiKey: process.env.MITRA_API_KEY });
|
|
310
|
+
* await mitra.auth.signInWithApiKey();
|
|
311
|
+
* ```
|
|
312
|
+
*/
|
|
313
|
+
signInWithApiKey(apiKey?: string | undefined): Promise<User>;
|
|
286
314
|
/**
|
|
287
315
|
* Completes a Google redirect response from `#codeMitra` and `#stateMitra`.
|
|
288
316
|
*
|
|
@@ -593,6 +621,11 @@ interface MitraClientConfig {
|
|
|
593
621
|
* Found in the Mitra Code Studio dashboard.
|
|
594
622
|
*/
|
|
595
623
|
appId: string;
|
|
624
|
+
/**
|
|
625
|
+
* Api key for server-side authentication, used by `auth.signInWithApiKey()`.
|
|
626
|
+
* Never set this in code that reaches a browser: it would ship in the bundle.
|
|
627
|
+
*/
|
|
628
|
+
apiKey?: string;
|
|
596
629
|
/**
|
|
597
630
|
* Base URL for the Mitra API (Kong Gateway).
|
|
598
631
|
* Injected automatically via `VITE_MITRA_API_URL` environment variable
|
package/dist/index.js
CHANGED
|
@@ -348,6 +348,42 @@ function adoptLegacySession(session) {
|
|
|
348
348
|
// src/modules/auth.ts
|
|
349
349
|
import { createAuthModule } from "@mitralab.io/sdk-core";
|
|
350
350
|
|
|
351
|
+
// src/modules/api-key-auth.ts
|
|
352
|
+
import { resolveApiKeyToken } from "@mitralab.io/sdk-core";
|
|
353
|
+
async function withoutLeakingKey(apiKey, call) {
|
|
354
|
+
try {
|
|
355
|
+
return await call();
|
|
356
|
+
} catch (error) {
|
|
357
|
+
if (error instanceof MitraApiError && error.message.includes(apiKey)) {
|
|
358
|
+
throw new MitraApiError(error.message.split(apiKey).join("[REDACTED]"), error.status);
|
|
359
|
+
}
|
|
360
|
+
throw error;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function assertServerRuntime() {
|
|
364
|
+
if (globalThis.window !== void 0) {
|
|
365
|
+
throw new MitraApiError(
|
|
366
|
+
"Api key authentication runs only on a server. In a browser the key would be served to every visitor; sign in with Google or Microsoft instead.",
|
|
367
|
+
400
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
async function resolveApiKeySession(publicClient, appId, apiKey) {
|
|
372
|
+
return resolveApiKeyToken(
|
|
373
|
+
(path, init) => withoutLeakingKey(
|
|
374
|
+
apiKey,
|
|
375
|
+
() => publicClient.request(path, {
|
|
376
|
+
method: "POST",
|
|
377
|
+
...init.body === void 0 ? {} : { body: init.body },
|
|
378
|
+
...init.bearer === void 0 ? {} : { headers: { Authorization: `Bearer ${init.bearer}` } }
|
|
379
|
+
})
|
|
380
|
+
),
|
|
381
|
+
appId,
|
|
382
|
+
apiKey,
|
|
383
|
+
coreErrors
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
351
387
|
// src/modules/google-auth.ts
|
|
352
388
|
import { expectObject } from "@mitralab.io/sdk-core";
|
|
353
389
|
var RESULT_TYPE = "mitra-oauth-result";
|
|
@@ -635,6 +671,7 @@ function isDefinitiveRefreshFailure(error) {
|
|
|
635
671
|
}
|
|
636
672
|
var AuthModule = class {
|
|
637
673
|
appId;
|
|
674
|
+
defaultApiKey;
|
|
638
675
|
_currentUser = null;
|
|
639
676
|
#accessToken = null;
|
|
640
677
|
#refreshToken = null;
|
|
@@ -655,6 +692,7 @@ var AuthModule = class {
|
|
|
655
692
|
const apiUrl = stripTrailingSlashes(
|
|
656
693
|
options.apiUrl ?? (trimmedIamBaseUrl.endsWith("/iam") ? trimmedIamBaseUrl.slice(0, -"/iam".length) : trimmedIamBaseUrl)
|
|
657
694
|
);
|
|
695
|
+
this.defaultApiKey = options.apiKey;
|
|
658
696
|
this.storageKey = `mitra_auth_${appId}`;
|
|
659
697
|
this.publicClient = new HttpClient({ baseUrl: iamBaseUrl, getToken: () => null });
|
|
660
698
|
this.authedClient = new HttpClient({
|
|
@@ -741,6 +779,51 @@ var AuthModule = class {
|
|
|
741
779
|
async signInWithGoogle(options = {}) {
|
|
742
780
|
return this.establishSession(await this.googleAuth.signIn(options));
|
|
743
781
|
}
|
|
782
|
+
/**
|
|
783
|
+
* Signs in as a process, with an api key instead of a person's SSO session.
|
|
784
|
+
*
|
|
785
|
+
* Use this where nobody can complete a redirect: a cron, a background collector, another
|
|
786
|
+
* service. Create the key in Settings -> API keys. A Business key reaches the published
|
|
787
|
+
* product, a Developer key adds authoring, and an Administrator key belongs to a workspace -
|
|
788
|
+
* from that one the platform issues this app's token, deciding then and there what the key's
|
|
789
|
+
* owner actually reaches here.
|
|
790
|
+
*
|
|
791
|
+
* The session is not written to storage and carries no refresh token: the key does not
|
|
792
|
+
* expire and is revoked by deleting it, so renewing means signing in again.
|
|
793
|
+
*
|
|
794
|
+
* Server runtimes only. In a browser the key would ship inside the bundle, so this method
|
|
795
|
+
* refuses to run there.
|
|
796
|
+
*
|
|
797
|
+
* @param apiKey - The key to use. Defaults to `apiKey` from the client configuration.
|
|
798
|
+
* @returns The user the key belongs to.
|
|
799
|
+
*
|
|
800
|
+
* @example
|
|
801
|
+
* ```typescript
|
|
802
|
+
* const mitra = createClient({ appId, apiUrl, apiKey: process.env.MITRA_API_KEY });
|
|
803
|
+
* await mitra.auth.signInWithApiKey();
|
|
804
|
+
* ```
|
|
805
|
+
*/
|
|
806
|
+
async signInWithApiKey(apiKey = this.defaultApiKey) {
|
|
807
|
+
assertServerRuntime();
|
|
808
|
+
if (!apiKey?.trim()) {
|
|
809
|
+
throw new MitraApiError(
|
|
810
|
+
"No api key was given. Pass one to signInWithApiKey, or set apiKey when creating the client.",
|
|
811
|
+
400
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
const token = await resolveApiKeySession(this.publicClient, this.appId, apiKey);
|
|
815
|
+
if (!this.belongsToConfiguredApp(token)) {
|
|
816
|
+
throw new MitraApiError("This api key does not reach the app this client is configured for.", 403);
|
|
817
|
+
}
|
|
818
|
+
this.invalidatePendingRefreshes();
|
|
819
|
+
this.#accessToken = token;
|
|
820
|
+
this.#refreshToken = null;
|
|
821
|
+
const user = await this.getCurrentUser();
|
|
822
|
+
this._currentUser = user;
|
|
823
|
+
this.notifySessionListeners();
|
|
824
|
+
this.notifyListeners();
|
|
825
|
+
return user;
|
|
826
|
+
}
|
|
744
827
|
/**
|
|
745
828
|
* Completes a Google redirect response from `#codeMitra` and `#stateMitra`.
|
|
746
829
|
*
|
|
@@ -1623,7 +1706,7 @@ function expectAppInfoResponse(value) {
|
|
|
1623
1706
|
};
|
|
1624
1707
|
}
|
|
1625
1708
|
function createClient(config) {
|
|
1626
|
-
const { appId, apiUrl, authPageUrl, onError } = config;
|
|
1709
|
+
const { appId, apiUrl, apiKey, authPageUrl, onError } = config;
|
|
1627
1710
|
const gatewayUrl = stripTrailingSlashes(apiUrl);
|
|
1628
1711
|
const iamUrl = `${gatewayUrl}/iam`;
|
|
1629
1712
|
const dataManagerUrl = `${gatewayUrl}/data-manager`;
|
|
@@ -1631,7 +1714,7 @@ function createClient(config) {
|
|
|
1631
1714
|
const integrationUrl = `${gatewayUrl}/integration`;
|
|
1632
1715
|
const codeStudioUrl = `${gatewayUrl}/code-studio`;
|
|
1633
1716
|
const copilotUrl = `${gatewayUrl}/copilot`;
|
|
1634
|
-
const authModule = new AuthModule(appId, iamUrl, { apiUrl: gatewayUrl, authPageUrl });
|
|
1717
|
+
const authModule = new AuthModule(appId, iamUrl, { apiUrl: gatewayUrl, authPageUrl, apiKey });
|
|
1635
1718
|
const authSession = getAuthSessionPort(authModule);
|
|
1636
1719
|
const onUnauthorized = (requestToken) => authSession.handleUnauthorized(requestToken);
|
|
1637
1720
|
const beforeAuthenticatedRequest = () => authModule.ensureFreshSession().then(() => void 0);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mitralab.io/platform-sdk",
|
|
3
|
-
"version": "1.1.0-beta.
|
|
3
|
+
"version": "1.1.0-beta.3",
|
|
4
4
|
"description": "JavaScript/TypeScript SDK for building apps on the Mitra Platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"url": "https://github.com/mitralab-dev/mitra-platform-sdk/issues"
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"@mitralab.io/sdk-core": "0.2.0-beta.
|
|
65
|
+
"@mitralab.io/sdk-core": "0.2.0-beta.2",
|
|
66
66
|
"mitra-interactions-sdk": "1.0.60-beta.48"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|