synomem 0.6.0 → 0.6.2
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/backend.d.ts.map +1 -1
- package/dist/backend.js +1 -1
- package/dist/backend.js.map +1 -1
- package/dist/cli.d.ts +4 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +57 -23
- package/dist/cli.js.map +1 -1
- package/dist/configure.d.ts +7 -4
- package/dist/configure.d.ts.map +1 -1
- package/dist/configure.js +33 -9
- package/dist/configure.js.map +1 -1
- package/dist/credentials.d.ts +12 -5
- package/dist/credentials.d.ts.map +1 -1
- package/dist/credentials.js.map +1 -1
- package/dist/discover.d.ts +9 -11
- package/dist/discover.d.ts.map +1 -1
- package/dist/discover.js +14 -15
- package/dist/discover.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/oauth.d.ts +15 -1
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js +26 -6
- package/dist/oauth.js.map +1 -1
- package/dist/remote.d.ts.map +1 -1
- package/dist/remote.js +9 -0
- package/dist/remote.js.map +1 -1
- package/docs/cli.md +4 -3
- package/package.json +1 -1
- package/src/backend.ts +2 -0
- package/src/cli.ts +78 -25
- package/src/configure.ts +33 -9
- package/src/credentials.ts +12 -5
- package/src/discover.ts +18 -19
- package/src/index.ts +5 -1
- package/src/oauth.ts +20 -5
- package/src/remote.ts +9 -0
package/src/cli.ts
CHANGED
|
@@ -21,14 +21,19 @@ import {
|
|
|
21
21
|
type ConfigPlan,
|
|
22
22
|
type CredentialStoreChoice,
|
|
23
23
|
} from './configure.js';
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
discoverAccessKeyWorkspaces,
|
|
26
|
+
discoverOrganizations,
|
|
27
|
+
workspaceChoices,
|
|
28
|
+
type DiscoveredWorkspace,
|
|
29
|
+
} from './discover.js';
|
|
25
30
|
import { DEFAULT_WORKSPACE, listLocalWorkspaces, localWorkspaceHome } from './workspaces.js';
|
|
26
31
|
import {
|
|
27
32
|
findProjectSelection,
|
|
28
33
|
resolveWorkspaceSelection,
|
|
29
34
|
writeProjectSelection,
|
|
30
35
|
} from './project.js';
|
|
31
|
-
import { defaultPromptIo, type PromptIo } from './prompt.js';
|
|
36
|
+
import { defaultPromptIo, select, type PromptIo } from './prompt.js';
|
|
32
37
|
import { credentialReference, OsCredentialStore, type CredentialStore } from './credentials.js';
|
|
33
38
|
import { asSynomemError, SynomemError, type SynomemErrorCode } from './errors.js';
|
|
34
39
|
import { atomicWriteFile } from './fs-utils.js';
|
|
@@ -84,12 +89,12 @@ export interface CliDependencies {
|
|
|
84
89
|
}) => Promise<void>;
|
|
85
90
|
/*
|
|
86
91
|
* Injected so setup can be tested without a network. The default asks the
|
|
87
|
-
* service which
|
|
92
|
+
* service which workspaces an access key can reach.
|
|
88
93
|
*/
|
|
89
|
-
|
|
94
|
+
discoverAccessKeyWorkspaces?: (options: {
|
|
90
95
|
baseUrl: string;
|
|
91
96
|
accessToken: string;
|
|
92
|
-
}) => Promise<{
|
|
97
|
+
}) => Promise<{ organizationId: string; workspaces: DiscoveredWorkspace[] }>;
|
|
93
98
|
createImportBundle?: (home: string) => Promise<ImportBundle>;
|
|
94
99
|
remoteImport?: (options: {
|
|
95
100
|
baseUrl: string;
|
|
@@ -419,7 +424,8 @@ export function createCli(
|
|
|
419
424
|
|
|
420
425
|
const credentialStore = dependencies.credentialStore ?? new OsCredentialStore();
|
|
421
426
|
const oauthLogin = dependencies.oauthLogin ?? loginWithOAuth;
|
|
422
|
-
const
|
|
427
|
+
const discoverWorkspaces =
|
|
428
|
+
dependencies.discoverAccessKeyWorkspaces ?? discoverAccessKeyWorkspaces;
|
|
423
429
|
const promptIo = dependencies.promptIo ?? defaultPromptIo();
|
|
424
430
|
const verifyRemoteCredential =
|
|
425
431
|
dependencies.verifyRemoteCredential ??
|
|
@@ -565,12 +571,12 @@ export function createCli(
|
|
|
565
571
|
const remoteCommand = program.command('remote').description('Administer a remote workspace');
|
|
566
572
|
|
|
567
573
|
/*
|
|
568
|
-
* The browser counterpart to
|
|
569
|
-
*
|
|
570
|
-
*
|
|
571
|
-
*
|
|
572
|
-
*
|
|
573
|
-
*
|
|
574
|
+
* The browser counterpart to `discoverAccessKeyWorkspaces` — same purpose,
|
|
575
|
+
* different credential: a signed-in account may reach several
|
|
576
|
+
* organizations, each with several workspaces, so there is a genuine choice
|
|
577
|
+
* to make, and no way to make it without seeing the list. Printing the IDs
|
|
578
|
+
* alongside the names is the point: the ID is what `backend use remote
|
|
579
|
+
* --workspace` takes.
|
|
574
580
|
*/
|
|
575
581
|
remoteCommand
|
|
576
582
|
.command('workspaces')
|
|
@@ -655,6 +661,8 @@ export function createCli(
|
|
|
655
661
|
credentialReference(backend.baseUrl, backend.workspaceId, administrator),
|
|
656
662
|
credentialStore,
|
|
657
663
|
env,
|
|
664
|
+
fetch,
|
|
665
|
+
resolveHome(global.home),
|
|
658
666
|
);
|
|
659
667
|
const importer = new RemoteImportClient({
|
|
660
668
|
baseUrl: backend.baseUrl,
|
|
@@ -723,18 +731,63 @@ export function createCli(
|
|
|
723
731
|
const serviceUrl = plan.serviceUrl ?? cloudApiUrl(env);
|
|
724
732
|
|
|
725
733
|
/*
|
|
726
|
-
*
|
|
727
|
-
*
|
|
728
|
-
*
|
|
729
|
-
*
|
|
730
|
-
*
|
|
731
|
-
* network round trip to configure a machine.
|
|
734
|
+
* A member-owned access key reaches every workspace in its organization,
|
|
735
|
+
* never just one, so the workspace is discovered and then chosen — not
|
|
736
|
+
* typed from memory, and not assumed. An explicit --workspace still wins
|
|
737
|
+
* regardless: automation should not depend on a network round trip, and a
|
|
738
|
+
* person who already knows which one they want should not be asked again.
|
|
732
739
|
*/
|
|
733
740
|
let workspaceId = plan.workspaceId;
|
|
734
741
|
if (plan.backend === 'remote' && !workspaceId && token) {
|
|
735
|
-
const
|
|
736
|
-
|
|
737
|
-
|
|
742
|
+
const discovered = await discoverWorkspaces({ baseUrl: serviceUrl, accessToken: token });
|
|
743
|
+
if (discovered.workspaces.length === 0) {
|
|
744
|
+
throw new SynomemError(
|
|
745
|
+
'INVALID_INPUT',
|
|
746
|
+
[
|
|
747
|
+
"This access key's organization has no workspaces yet.",
|
|
748
|
+
'Create one in the Synomem portal, then re-run this command — or pass',
|
|
749
|
+
'--workspace <workspace-id> once one exists.',
|
|
750
|
+
].join('\n'),
|
|
751
|
+
);
|
|
752
|
+
} else if (discovered.workspaces.length === 1) {
|
|
753
|
+
workspaceId = discovered.workspaces[0]!.id;
|
|
754
|
+
/*
|
|
755
|
+
* The key itself reaches every workspace this organization has —
|
|
756
|
+
* there is just one to choose from today. Wording this as the key's
|
|
757
|
+
* own workspace ("this key's workspace") was a repeated, corrected
|
|
758
|
+
* mistake: it is this MACHINE's choice of which workspace to act in
|
|
759
|
+
* with the key, not a property of the key.
|
|
760
|
+
*/
|
|
761
|
+
io.stdout(
|
|
762
|
+
`This machine will use ${discovered.workspaces[0]!.displayName} (${workspaceId}) — ` +
|
|
763
|
+
'the only workspace this key currently reaches. Run `synomem backend use ' +
|
|
764
|
+
'remote --workspace <workspace-id>` to point it at a different one later.\n',
|
|
765
|
+
);
|
|
766
|
+
} else if (promptIo.interactive) {
|
|
767
|
+
workspaceId = await select(
|
|
768
|
+
promptIo,
|
|
769
|
+
'Which workspace should this machine use?',
|
|
770
|
+
discovered.workspaces.map((workspace) => ({
|
|
771
|
+
value: workspace.id,
|
|
772
|
+
label: workspace.displayName,
|
|
773
|
+
detail: workspace.id,
|
|
774
|
+
})),
|
|
775
|
+
);
|
|
776
|
+
} else {
|
|
777
|
+
throw new SynomemError(
|
|
778
|
+
'INVALID_INPUT',
|
|
779
|
+
[
|
|
780
|
+
'This access key can reach more than one workspace, so a non-interactive',
|
|
781
|
+
'setup needs to be told which one:',
|
|
782
|
+
'',
|
|
783
|
+
...discovered.workspaces.map(
|
|
784
|
+
(workspace) => ` ${workspace.id} ${workspace.displayName}`,
|
|
785
|
+
),
|
|
786
|
+
'',
|
|
787
|
+
'Re-run with --workspace <workspace-id>.',
|
|
788
|
+
].join('\n'),
|
|
789
|
+
);
|
|
790
|
+
}
|
|
738
791
|
}
|
|
739
792
|
if (plan.backend === 'remote' && !workspaceId) {
|
|
740
793
|
/*
|
|
@@ -846,7 +899,7 @@ export function createCli(
|
|
|
846
899
|
.option('--credential-store <where>', 'auto, keychain, file, or environment', 'auto')
|
|
847
900
|
// The token is read from stdin, never taken as an argument: an argument is
|
|
848
901
|
// kept by the shell history and visible in the process list.
|
|
849
|
-
.option('--access-token-stdin', 'read the
|
|
902
|
+
.option('--access-token-stdin', 'read the access key from stdin', false)
|
|
850
903
|
.option('--yes', 'apply without confirming', false)
|
|
851
904
|
.action(
|
|
852
905
|
async (
|
|
@@ -864,12 +917,12 @@ export function createCli(
|
|
|
864
917
|
throw new SynomemError('INVALID_INPUT', 'Pass --backend local or --backend remote.');
|
|
865
918
|
}
|
|
866
919
|
const backend: BackendChoice = options.backend;
|
|
867
|
-
// An access key
|
|
868
|
-
// required when there is no key to ask.
|
|
920
|
+
// An access key can be asked which workspaces it reaches, so
|
|
921
|
+
// --workspace is only required when there is no key to ask.
|
|
869
922
|
if (backend === 'remote' && !global.workspace && !options.accessTokenStdin) {
|
|
870
923
|
throw new SynomemError(
|
|
871
924
|
'INVALID_INPUT',
|
|
872
|
-
'Remote setup requires --workspace, or --access-token-stdin so the key can
|
|
925
|
+
'Remote setup requires --workspace, or --access-token-stdin so the key can be asked.',
|
|
873
926
|
);
|
|
874
927
|
}
|
|
875
928
|
const token = options.accessTokenStdin ? await readAccessToken(promptIo) : undefined;
|
package/src/configure.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* that blocks forever on a pipe is worse than one that says which flags it
|
|
8
8
|
* needs.
|
|
9
9
|
*/
|
|
10
|
-
import { chmodSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
10
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
11
11
|
import { join } from 'node:path';
|
|
12
12
|
import { cloudApiUrl } from './cloud.js';
|
|
13
13
|
import { SynomemError } from './errors.js';
|
|
@@ -117,16 +117,39 @@ export function assertInteractive(io: PromptIo): void {
|
|
|
117
117
|
* Separate from `config.json` so a configuration file can be read, copied or
|
|
118
118
|
* pasted into an issue without carrying a secret with it.
|
|
119
119
|
*/
|
|
120
|
+
function credentialFilePath(home: string): string {
|
|
121
|
+
return join(home, 'credentials', 'installation.json');
|
|
122
|
+
}
|
|
123
|
+
|
|
120
124
|
export function writeCredentialFile(home: string, token: string): string {
|
|
121
125
|
const directory = join(home, 'credentials');
|
|
122
126
|
mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
123
127
|
chmodSync(directory, 0o700);
|
|
124
|
-
const path =
|
|
128
|
+
const path = credentialFilePath(home);
|
|
125
129
|
writeFileSync(path, `${JSON.stringify({ accessToken: token }, null, 2)}\n`, { mode: 0o600 });
|
|
126
130
|
chmodSync(path, 0o600);
|
|
127
131
|
return path;
|
|
128
132
|
}
|
|
129
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Reads back what `writeCredentialFile` wrote.
|
|
136
|
+
*
|
|
137
|
+
* Without this, choosing the restricted-file store at `synomem config` wrote
|
|
138
|
+
* a credential nothing ever read again: every later command still asked the
|
|
139
|
+
* OS keychain, found nothing there, and failed with AUTH_REQUIRED even though
|
|
140
|
+
* the key was sitting right there on disk.
|
|
141
|
+
*/
|
|
142
|
+
export function readCredentialFile(home: string): string | undefined {
|
|
143
|
+
const path = credentialFilePath(home);
|
|
144
|
+
if (!existsSync(path)) return undefined;
|
|
145
|
+
try {
|
|
146
|
+
const parsed = JSON.parse(readFileSync(path, 'utf8')) as { accessToken?: unknown };
|
|
147
|
+
return typeof parsed.accessToken === 'string' ? parsed.accessToken : undefined;
|
|
148
|
+
} catch {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
130
153
|
/** A key's identifying prefix. Never the key. */
|
|
131
154
|
export function credentialFingerprint(token: string): string {
|
|
132
155
|
const head = token.slice(0, 12);
|
|
@@ -194,8 +217,8 @@ export async function runConfigWizard(
|
|
|
194
217
|
},
|
|
195
218
|
{
|
|
196
219
|
value: 'access-key',
|
|
197
|
-
label: 'Use an
|
|
198
|
-
detail: 'Create one at https://portal.synomem.ai/
|
|
220
|
+
label: 'Use an access key',
|
|
221
|
+
detail: 'Create one at https://portal.synomem.ai/access-keys/new',
|
|
199
222
|
},
|
|
200
223
|
]);
|
|
201
224
|
|
|
@@ -203,10 +226,11 @@ export async function runConfigWizard(
|
|
|
203
226
|
* Deliberately no workspace prompt here.
|
|
204
227
|
*
|
|
205
228
|
* A hosted workspace ID looks like `ws-04psqx2rkt8ttft7a1t2z69r97`, and the
|
|
206
|
-
* credential authorized in the next step already knows
|
|
207
|
-
*
|
|
208
|
-
* sign-in can list the ones the account belongs to
|
|
209
|
-
* asking a person to go and look something
|
|
229
|
+
* credential authorized in the next step already knows the answer -- an
|
|
230
|
+
* access key can list every workspace its organization has, and a browser
|
|
231
|
+
* sign-in can list the ones the account belongs to across every
|
|
232
|
+
* organization. Asking first means asking a person to go and look something
|
|
233
|
+
* up that we are about to be told.
|
|
210
234
|
*/
|
|
211
235
|
|
|
212
236
|
const credentialStore =
|
|
@@ -223,7 +247,7 @@ export async function runConfigWizard(
|
|
|
223
247
|
|
|
224
248
|
/** Reads an access key without ever accepting it as an argument. */
|
|
225
249
|
export async function readAccessToken(io: PromptIo): Promise<string> {
|
|
226
|
-
const token = await askSecret(io, '
|
|
250
|
+
const token = await askSecret(io, 'Access key');
|
|
227
251
|
if (!token) throw new SynomemError('INVALID_INPUT', 'No access key was provided.');
|
|
228
252
|
return token;
|
|
229
253
|
}
|
package/src/credentials.ts
CHANGED
|
@@ -7,12 +7,19 @@ const serviceName = 'ai.synomem.credentials';
|
|
|
7
7
|
const maximumOutputBytes = 128 * 1024;
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* A member-owned access key.
|
|
11
11
|
*
|
|
12
|
-
* Not an OAuth credential: it has no refresh, no token endpoint and no
|
|
13
|
-
* and it authorizes
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* Not an OAuth credential: it has no refresh, no token endpoint and no
|
|
13
|
+
* client, and it authorizes the MEMBER who created it, reaching every
|
|
14
|
+
* workspace their organization membership allows -- never a single machine
|
|
15
|
+
* or a single workspace. Keeping it a distinct shape stops code treating it
|
|
16
|
+
* as refreshable, which would mean silently failing to renew something that
|
|
17
|
+
* never expires that way.
|
|
18
|
+
*
|
|
19
|
+
* The `kind` value and this type's own name predate member-owned access
|
|
20
|
+
* keys; both still say "installation" because renaming either changes the
|
|
21
|
+
* shape already written to disk and the OS keychain on every machine that
|
|
22
|
+
* has run `synomem config`, for no behavioral gain.
|
|
16
23
|
*/
|
|
17
24
|
export interface StoredInstallationKey {
|
|
18
25
|
kind: 'installation-key';
|
package/src/discover.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Finding out which
|
|
2
|
+
* Finding out which workspaces a credential can reach, so nobody has to type
|
|
3
3
|
* one from memory.
|
|
4
4
|
*
|
|
5
5
|
* A hosted workspace ID looks like `ws-04psqx2rkt8ttft7a1t2z69r97`. Asking a
|
|
@@ -9,11 +9,12 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Two credentials, two routes, because they carry different authority:
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* A member-owned access key authenticates the ACCOUNT that created it, and
|
|
13
|
+
* reaches every workspace in that account's organization -- never just one
|
|
14
|
+
* -- so `discoverAccessKeyWorkspaces` lists them off the data plane, with no
|
|
15
|
+
* workspace chosen yet, which is exactly what setup cannot supply up front.
|
|
15
16
|
*
|
|
16
|
-
* A browser sign-in authorizes an ACCOUNT, which may reach several
|
|
17
|
+
* A browser sign-in also authorizes an ACCOUNT, which may reach several
|
|
17
18
|
* organizations, each with several workspaces. `discoverOrganizations` lists
|
|
18
19
|
* them for selection.
|
|
19
20
|
*/
|
|
@@ -82,23 +83,21 @@ async function readJson<T>(options: DiscoveryOptions, path: string): Promise<T>
|
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
/**
|
|
85
|
-
*
|
|
86
|
+
* Every workspace a member-owned access key can reach, asked before any one
|
|
87
|
+
* of them has been chosen.
|
|
86
88
|
*
|
|
87
|
-
* Answered by the data plane rather than the control plane: an
|
|
88
|
-
*
|
|
89
|
-
*
|
|
89
|
+
* Answered by the data plane rather than the control plane: an access key is
|
|
90
|
+
* not an account principal the control plane recognizes, but the data plane
|
|
91
|
+
* already knows which organization owns it and can list that organization's
|
|
92
|
+
* workspaces without requiring one to be named first.
|
|
90
93
|
*/
|
|
91
|
-
export async function
|
|
94
|
+
export async function discoverAccessKeyWorkspaces(
|
|
92
95
|
options: DiscoveryOptions,
|
|
93
|
-
): Promise<{
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (!identity.workspaceId) {
|
|
99
|
-
throw new SynomemError('REMOTE_PROTOCOL', 'The service did not report a bound workspace.');
|
|
100
|
-
}
|
|
101
|
-
return { workspaceId: identity.workspaceId, actor: identity.actor };
|
|
96
|
+
): Promise<{ organizationId: string; workspaces: DiscoveredWorkspace[] }> {
|
|
97
|
+
return await readJson<{ organizationId: string; workspaces: DiscoveredWorkspace[] }>(
|
|
98
|
+
options,
|
|
99
|
+
'v1/access-keys/workspaces',
|
|
100
|
+
);
|
|
102
101
|
}
|
|
103
102
|
|
|
104
103
|
/**
|
package/src/index.ts
CHANGED
|
@@ -58,7 +58,11 @@ export {
|
|
|
58
58
|
writeProjectSelection,
|
|
59
59
|
} from './project.js';
|
|
60
60
|
export type { ProjectConfig, ProjectSelection } from './project.js';
|
|
61
|
-
export {
|
|
61
|
+
export {
|
|
62
|
+
discoverAccessKeyWorkspaces,
|
|
63
|
+
discoverOrganizations,
|
|
64
|
+
workspaceChoices,
|
|
65
|
+
} from './discover.js';
|
|
62
66
|
export type { DiscoveredOrganization, DiscoveredWorkspace, DiscoveryOptions } from './discover.js';
|
|
63
67
|
export { asSynomemError, errorCodes, SynomemError } from './errors.js';
|
|
64
68
|
export {
|
package/src/oauth.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
import { SynomemError } from './errors.js';
|
|
9
9
|
import type { CredentialStore, StoredOAuthCredential } from './credentials.js';
|
|
10
10
|
import type { SynomemCredentialProvider } from './remote.js';
|
|
11
|
+
import { readCredentialFile } from './configure.js';
|
|
11
12
|
|
|
12
13
|
const defaultScope = 'synomem:read synomem:write offline_access';
|
|
13
14
|
|
|
@@ -195,6 +196,8 @@ export async function loginWithOAuth(options: OAuthLoginOptions): Promise<void>
|
|
|
195
196
|
export class StoredCredentialProvider implements SynomemCredentialProvider {
|
|
196
197
|
private refresh?: Promise<string | undefined>;
|
|
197
198
|
private credential?: StoredOAuthCredential;
|
|
199
|
+
/** Set when the stored credential is an access key rather than an OAuth grant. */
|
|
200
|
+
private staticAccessToken?: string;
|
|
198
201
|
private loaded = false;
|
|
199
202
|
|
|
200
203
|
constructor(
|
|
@@ -202,6 +205,12 @@ export class StoredCredentialProvider implements SynomemCredentialProvider {
|
|
|
202
205
|
private readonly store: CredentialStore,
|
|
203
206
|
private readonly env: NodeJS.ProcessEnv = process.env,
|
|
204
207
|
private readonly fetchImplementation: typeof fetch = fetch,
|
|
208
|
+
/**
|
|
209
|
+
* Where `synomem config`'s restricted-file credential store, if chosen,
|
|
210
|
+
* would have written an access key. Optional because local-only and
|
|
211
|
+
* environment-only callers have no such file to fall back to.
|
|
212
|
+
*/
|
|
213
|
+
private readonly home?: string,
|
|
205
214
|
) {}
|
|
206
215
|
|
|
207
216
|
async getAccessToken(signal?: AbortSignal): Promise<string | undefined> {
|
|
@@ -209,14 +218,20 @@ export class StoredCredentialProvider implements SynomemCredentialProvider {
|
|
|
209
218
|
if (!this.loaded) {
|
|
210
219
|
const stored = await this.store.get(this.reference);
|
|
211
220
|
/*
|
|
212
|
-
* An
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
* ignores it and lets the installation-key path handle it.
|
|
221
|
+
* An access key is not an OAuth credential: it cannot be refreshed and
|
|
222
|
+
* has no client or token endpoint. It is used exactly as stored,
|
|
223
|
+
* whichever of the two places it was found.
|
|
216
224
|
*/
|
|
217
|
-
|
|
225
|
+
if (stored && 'kind' in stored) {
|
|
226
|
+
this.staticAccessToken = stored.accessToken;
|
|
227
|
+
} else if (stored) {
|
|
228
|
+
this.credential = stored;
|
|
229
|
+
} else if (this.home) {
|
|
230
|
+
this.staticAccessToken = readCredentialFile(this.home);
|
|
231
|
+
}
|
|
218
232
|
this.loaded = true;
|
|
219
233
|
}
|
|
234
|
+
if (this.staticAccessToken) return this.staticAccessToken;
|
|
220
235
|
const credential = this.credential;
|
|
221
236
|
if (!credential) return undefined;
|
|
222
237
|
if (!credential.expiresAt || credential.expiresAt > Date.now()) return credential.accessToken;
|
package/src/remote.ts
CHANGED
|
@@ -647,6 +647,15 @@ export class RemoteSynomemService implements SynomemService {
|
|
|
647
647
|
headers: {
|
|
648
648
|
accept: 'application/json',
|
|
649
649
|
authorization: `Bearer ${accessToken}`,
|
|
650
|
+
/*
|
|
651
|
+
* Harmless for an OAuth actor token (its workspace and identity are
|
|
652
|
+
* already bound into the token itself), and required for an access
|
|
653
|
+
* key: an access key authenticates the member who owns it, not a
|
|
654
|
+
* machine or a workspace, so which workspace and which agent are
|
|
655
|
+
* meant have to be named on every request.
|
|
656
|
+
*/
|
|
657
|
+
'synomem-workspace-id': this.workspaceId,
|
|
658
|
+
...(this.actor.kind === 'agent' ? { 'synomem-agent-id': this.actor.id } : {}),
|
|
650
659
|
...(body ? { 'content-type': 'application/json' } : {}),
|
|
651
660
|
...(idempotencyKey ? { 'idempotency-key': idempotencyKey } : {}),
|
|
652
661
|
},
|