@faable/faable 3.2.0 → 3.3.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/api/auth.js +44 -13
- package/dist/api/auth_admin.js +5 -8
- package/dist/api/session.js +4 -1
- package/package.json +2 -2
package/dist/api/auth.js
CHANGED
|
@@ -1,8 +1,41 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { FaableAuthApi, authBearer } from '@faable/auth-sdk';
|
|
2
2
|
import os from 'os';
|
|
3
|
+
import { version } from '../config.js';
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
// The CLI talks to the Faable tenant's auth server through the same SDK the
|
|
6
|
+
// rest of the platform uses. It used to be a bare axios instance, which meant
|
|
7
|
+
// every call landed in the tenant's traffic as "not identified" — the SDK
|
|
8
|
+
// stamps `x-faable-client`, and the value below makes it say who this really
|
|
9
|
+
// is instead of `auth-sdk`.
|
|
10
|
+
//
|
|
11
|
+
// Two clients, because there are two kinds of call:
|
|
12
|
+
// - anonymous: the device flow and the refresh grant. A public client, no
|
|
13
|
+
// secret, no token yet. POSTs are never retried by the fetcher, which is
|
|
14
|
+
// what the device polling needs — every `authorization_pending` is an
|
|
15
|
+
// error the loop expects, not one to replay.
|
|
16
|
+
// - bearer: `/me` and everything `faable auth` does, with the token the user
|
|
17
|
+
// already holds. The strategy asks for the token on every request, so
|
|
18
|
+
// whatever `loadLiveCredentials` refreshed is what goes on the wire.
|
|
19
|
+
const AUTH_DOMAIN = "https://faable.auth.faable.link";
|
|
5
20
|
const CLIENT_ID = "c879023b-e34f-4b0c-a262-210e556bc2e4";
|
|
21
|
+
// `<name>/<version>`. No commit: the CLI has no release SHA constant, and a
|
|
22
|
+
// dev build (`0.0.0-development`) is identifiable as such by the version
|
|
23
|
+
// alone. auth caps this segment at 32 chars.
|
|
24
|
+
const CLI_CLIENT = `faable-cli/${version}`;
|
|
25
|
+
// No `x-faable-instance`: the SDK's default is `${HOSTNAME}:${pid}`, which on
|
|
26
|
+
// a laptop is the user's machine name in our audit log. An empty string
|
|
27
|
+
// suppresses the header (the helper drops falsy parts).
|
|
28
|
+
const clientInfo = { client: CLI_CLIENT, instance: "" };
|
|
29
|
+
const createAnonymousAuthApi = ({ domain = AUTH_DOMAIN, fetcher, } = {}) => FaableAuthApi.create({ domain, clientInfo, ...(fetcher ? { fetcher } : {}) });
|
|
30
|
+
const createBearerAuthApi = (token, { domain = AUTH_DOMAIN, account, fetcher } = {}) => FaableAuthApi.create({
|
|
31
|
+
domain,
|
|
32
|
+
...(account ? { headers: { account_id: account } } : {}),
|
|
33
|
+
authStrategy: authBearer,
|
|
34
|
+
auth: { token },
|
|
35
|
+
clientInfo,
|
|
36
|
+
...(fetcher ? { fetcher } : {}),
|
|
37
|
+
});
|
|
38
|
+
const anonymous = createAnonymousAuthApi();
|
|
6
39
|
const PLATFORM_LABELS = {
|
|
7
40
|
darwin: "macOS",
|
|
8
41
|
win32: "Windows",
|
|
@@ -15,35 +48,33 @@ function deviceName() {
|
|
|
15
48
|
return `${os.hostname()} (${platform})`;
|
|
16
49
|
}
|
|
17
50
|
async function getDeviceCode() {
|
|
18
|
-
|
|
51
|
+
return anonymous.fetcher.post(`/oauth/device/code`, {
|
|
19
52
|
client_id: CLIENT_ID,
|
|
20
53
|
scope: "openid email profile offline_access",
|
|
21
54
|
device_name: deviceName(),
|
|
22
55
|
});
|
|
23
|
-
return res.data;
|
|
24
56
|
}
|
|
25
57
|
async function getDeviceToken(device_code) {
|
|
26
|
-
|
|
27
|
-
|
|
58
|
+
return anonymous.fetcher.post(`/oauth/token`, {
|
|
59
|
+
device_code,
|
|
60
|
+
client_id: CLIENT_ID,
|
|
61
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
62
|
+
});
|
|
28
63
|
}
|
|
29
64
|
// Validate a device-flow access token against the Faable Auth server. The token
|
|
30
65
|
// is issued by the auth server, so it must be introspected there — NOT against
|
|
31
66
|
// the deploy API (api.faable.com), which has no /me route and would 404.
|
|
32
67
|
async function getMe(access_token) {
|
|
33
|
-
|
|
34
|
-
headers: { Authorization: `Bearer ${access_token}` },
|
|
35
|
-
});
|
|
36
|
-
return res.data;
|
|
68
|
+
return createBearerAuthApi(access_token).fetcher.get(`/me`);
|
|
37
69
|
}
|
|
38
70
|
// Exchange a refresh token for a fresh access token (and a rotated refresh
|
|
39
71
|
// token). The CLI is a public client, so no client_secret is required.
|
|
40
72
|
async function refreshToken(refresh_token) {
|
|
41
|
-
|
|
73
|
+
return anonymous.fetcher.post(`/oauth/token`, {
|
|
42
74
|
grant_type: "refresh_token",
|
|
43
75
|
client_id: CLIENT_ID,
|
|
44
76
|
refresh_token,
|
|
45
77
|
});
|
|
46
|
-
return res.data;
|
|
47
78
|
}
|
|
48
79
|
|
|
49
|
-
export { getDeviceCode, getDeviceToken, getMe, refreshToken };
|
|
80
|
+
export { AUTH_DOMAIN, CLIENT_ID, CLI_CLIENT, createAnonymousAuthApi, createBearerAuthApi, getDeviceCode, getDeviceToken, getMe, refreshToken };
|
package/dist/api/auth_admin.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { FaableAuthApi } from '@faable/auth-sdk';
|
|
2
1
|
import { CredentialsStore } from '../lib/CredentialsStore.js';
|
|
3
2
|
import { log } from '../log.js';
|
|
3
|
+
import { createBearerAuthApi } from './auth.js';
|
|
4
4
|
import { loadLiveCredentials } from './session.js';
|
|
5
5
|
|
|
6
6
|
// Default tenant host. `faable auth` is customer-facing: a customer targets
|
|
@@ -31,13 +31,10 @@ const requireAuthAdmin = async (opts = {}) => {
|
|
|
31
31
|
}
|
|
32
32
|
const domain = opts.authUrl || process.env.FAABLE_AUTH_URL || DEFAULT_AUTH_URL;
|
|
33
33
|
const account = opts.account || process.env.FAABLE_AUTH_ACCOUNT;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
// strategy, so this header is used verbatim on every request.
|
|
39
|
-
fetcher: { headers: { Authorization: `Bearer ${token}` } }
|
|
40
|
-
});
|
|
34
|
+
// The session's bearer as a strategy — with the CLI's own identity on the
|
|
35
|
+
// wire instead of `auth-sdk` — scoped to the target tenant when one was
|
|
36
|
+
// named. The server decides whether this token may manage that tenant.
|
|
37
|
+
return createBearerAuthApi(token, { domain, account });
|
|
41
38
|
};
|
|
42
39
|
// Translate raw management-API failures into actionable CLI errors. Everything
|
|
43
40
|
// else is rethrown untouched (FaableApiError messages already carry
|
package/dist/api/session.js
CHANGED
|
@@ -49,7 +49,10 @@ const loadLiveCredentials = async (store = new CredentialsStore()) => {
|
|
|
49
49
|
// token endpoint's RFC 6749 error body. "Run `faable login` again"
|
|
50
50
|
// would be a lie here — login is denied too — so say what actually
|
|
51
51
|
// happened and stop instead of letting a generic 401 mislead.
|
|
52
|
-
|
|
52
|
+
// The SDK's error keeps the HTTP body under `response.data`, like the
|
|
53
|
+
// axios error it replaced.
|
|
54
|
+
const body = e
|
|
55
|
+
?.response?.data;
|
|
53
56
|
if (body?.error_code === "user_suspended") {
|
|
54
57
|
log.error("❌ Your account has been suspended. Contact support@faable.com.");
|
|
55
58
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@faable/faable",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Marc Pomar <marc@faable.com>",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@actions/core": "^3.0.0",
|
|
30
|
-
"@faable/auth-sdk": "^2.
|
|
30
|
+
"@faable/auth-sdk": "^2.6.0",
|
|
31
31
|
"axios": "^1.18.1",
|
|
32
32
|
"fs-extra": "^11.3.2",
|
|
33
33
|
"open": "^11.0.0",
|