@faable/faable 3.4.0 → 3.4.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/dist/api/FaableApi.js +8 -0
- package/dist/api/auth_admin.js +48 -6
- package/package.json +1 -1
package/dist/api/FaableApi.js
CHANGED
|
@@ -102,6 +102,14 @@ class FaableApi {
|
|
|
102
102
|
static create(config = {}) {
|
|
103
103
|
return new FaableApi(config);
|
|
104
104
|
}
|
|
105
|
+
// A short-lived Management API token for ONE Faable Auth tenant, issued by
|
|
106
|
+
// the deploy api after checking the caller belongs to the project that owns
|
|
107
|
+
// it (phase 2 of arch/auth/management-api-tenant-isolation.md). `faable auth`
|
|
108
|
+
// manages tenants with it instead of the `faable login` token, which stops
|
|
109
|
+
// being platform-superadmin in phase 3.
|
|
110
|
+
async issueAuthAccountToken(account_id) {
|
|
111
|
+
return data(this.client.post(`/auth-accounts/${account_id}/token`));
|
|
112
|
+
}
|
|
105
113
|
async list() {
|
|
106
114
|
return allPages(next => data(this.client.get(`/app`, {
|
|
107
115
|
params: { pageSize: 200, ...(next ? { next } : {}) }
|
package/dist/api/auth_admin.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { CredentialsStore } from '../lib/CredentialsStore.js';
|
|
2
2
|
import { log } from '../log.js';
|
|
3
|
-
import {
|
|
3
|
+
import { FaableApi } from './FaableApi.js';
|
|
4
|
+
import { createBearerAuthApi, createAnonymousAuthApi } from './auth.js';
|
|
4
5
|
import { loadLiveCredentials } from './session.js';
|
|
6
|
+
import { bearer_strategy } from './strategies/bearer.strategy.js';
|
|
5
7
|
|
|
6
8
|
// Default tenant host. `faable auth` is customer-facing: a customer targets
|
|
7
9
|
// their own tenant with --auth-url https://<account>.auth.faable.link (or
|
|
@@ -31,10 +33,50 @@ const requireAuthAdmin = async (opts = {}) => {
|
|
|
31
33
|
}
|
|
32
34
|
const domain = opts.authUrl || process.env.FAABLE_AUTH_URL || DEFAULT_AUTH_URL;
|
|
33
35
|
const account = opts.account || process.env.FAABLE_AUTH_ACCOUNT;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
//
|
|
37
|
-
|
|
36
|
+
const tenant = account ?? (await resolveAccountByHost(domain));
|
|
37
|
+
const tenantToken = tenant ? await issueTenantToken(token, tenant) : undefined;
|
|
38
|
+
// Prefer the per-tenant management token; the session's bearer is the
|
|
39
|
+
// fallback (the platform tenant, or an api that can't issue one), exactly
|
|
40
|
+
// what `faable auth` sent before phase 2. Either way, scoped to the tenant.
|
|
41
|
+
return createBearerAuthApi(tenantToken ?? token, {
|
|
42
|
+
domain,
|
|
43
|
+
account: tenant ?? account
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
// The tenant behind an Auth host (`<slug>.auth.faable.link` or a custom
|
|
47
|
+
// domain), through the anonymous public lookup. `undefined` when it can't be
|
|
48
|
+
// resolved: the server then picks the tenant from the host, as before.
|
|
49
|
+
const resolveAccountByHost = async (domain, auth = createAnonymousAuthApi({ domain })) => {
|
|
50
|
+
try {
|
|
51
|
+
const host = new URL(domain).host;
|
|
52
|
+
const account = await auth.fetcher.get(`/account/host/${host}`);
|
|
53
|
+
return account?.id;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
// Statuses that mean "no per-tenant token for this one right now" — the
|
|
60
|
+
// platform tenant (404 tenant_token_not_issued), an api without the endpoint,
|
|
61
|
+
// or auth unable to mint. Anything else (403: not a member) is a real refusal.
|
|
62
|
+
const FALLBACK_STATUSES = new Set([404, 501, 503]);
|
|
63
|
+
const issueTenantToken = async (session_token, account_id, api = FaableApi.create({
|
|
64
|
+
authStrategy: bearer_strategy,
|
|
65
|
+
auth: { token: session_token }
|
|
66
|
+
})) => {
|
|
67
|
+
try {
|
|
68
|
+
const { access_token } = await api.issueAuthAccountToken(account_id);
|
|
69
|
+
return access_token;
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
const status = e?.response?.status;
|
|
73
|
+
if (status === 403) {
|
|
74
|
+
throw new Error(`Forbidden (403): you are not a member of the project that owns ${account_id}, so you can't manage it.`, { cause: e });
|
|
75
|
+
}
|
|
76
|
+
if (status === undefined || FALLBACK_STATUSES.has(status))
|
|
77
|
+
return undefined;
|
|
78
|
+
throw e;
|
|
79
|
+
}
|
|
38
80
|
};
|
|
39
81
|
// Translate raw management-API failures into actionable CLI errors. Everything
|
|
40
82
|
// else is rethrown untouched (FaableApiError messages already carry
|
|
@@ -61,4 +103,4 @@ const withAuthHints = (handler) => {
|
|
|
61
103
|
};
|
|
62
104
|
};
|
|
63
105
|
|
|
64
|
-
export { hintAuthError, requireAuthAdmin, withAuthHints };
|
|
106
|
+
export { hintAuthError, issueTenantToken, requireAuthAdmin, resolveAccountByHost, withAuthHints };
|