opencode-1password-auth 1.0.0 → 1.0.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/index.ts +86 -0
- package/package.json +1 -1
- package/setup.ps1 +93 -71
package/index.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { Plugin } from "@opencode-ai/plugin";
|
|
2
2
|
import * as sdk from "@1password/sdk";
|
|
3
|
+
import * as fs from "fs";
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
import * as os from "os";
|
|
3
6
|
|
|
4
7
|
interface MCPServerConfig {
|
|
5
8
|
environment?: Record<string, string>;
|
|
@@ -9,6 +12,18 @@ interface OpenCodeConfig {
|
|
|
9
12
|
mcp?: Record<string, MCPServerConfig>;
|
|
10
13
|
}
|
|
11
14
|
|
|
15
|
+
interface ProviderAuth {
|
|
16
|
+
type: string;
|
|
17
|
+
key: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface AuthJson {
|
|
21
|
+
[providerId: string]: ProviderAuth;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const AUTH_JSON_PATH = path.join(os.homedir(), ".local", "share", "opencode", "auth.json");
|
|
25
|
+
const OPENCODE_JSON_PATH = path.join(os.homedir(), ".config", "opencode", "opencode.json");
|
|
26
|
+
|
|
12
27
|
export const OnePasswordAuthPlugin: Plugin = async (ctx) => {
|
|
13
28
|
let opClient: Awaited<ReturnType<typeof sdk.createClient>> | null = null;
|
|
14
29
|
let mcpsEnvId: string | undefined;
|
|
@@ -55,6 +70,74 @@ export const OnePasswordAuthPlugin: Plugin = async (ctx) => {
|
|
|
55
70
|
return secrets;
|
|
56
71
|
};
|
|
57
72
|
|
|
73
|
+
const updateAuthJson = async (providerEnvId: string): Promise<void> => {
|
|
74
|
+
if (!fs.existsSync(AUTH_JSON_PATH)) {
|
|
75
|
+
console.log("1Password: auth.json not found, skipping");
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const content = fs.readFileSync(AUTH_JSON_PATH, "utf-8");
|
|
81
|
+
const auth: AuthJson = JSON.parse(content);
|
|
82
|
+
let modified = false;
|
|
83
|
+
|
|
84
|
+
for (const [providerId, authConfig] of Object.entries(auth)) {
|
|
85
|
+
if (authConfig.key && !authConfig.key.startsWith("{env:")) {
|
|
86
|
+
// Replace hardcoded key with env var reference
|
|
87
|
+
authConfig.key = `{env:${providerId}}`;
|
|
88
|
+
modified = true;
|
|
89
|
+
console.log(`1Password: Updated auth.json - ${providerId} -> {env:${providerId}}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (modified) {
|
|
94
|
+
fs.writeFileSync(AUTH_JSON_PATH, JSON.stringify(auth, null, 2));
|
|
95
|
+
console.log("1Password: auth.json updated to use environment variables");
|
|
96
|
+
}
|
|
97
|
+
} catch (err) {
|
|
98
|
+
console.error("1Password: Failed to update auth.json:", err);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const updateOpenCodeJsonMCP = async (mcpEnvId: string): Promise<void> => {
|
|
103
|
+
if (!fs.existsSync(OPENCODE_JSON_PATH)) {
|
|
104
|
+
console.log("1Password: opencode.json not found, skipping");
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
const content = fs.readFileSync(OPENCODE_JSON_PATH, "utf-8");
|
|
110
|
+
const config = JSON.parse(content);
|
|
111
|
+
|
|
112
|
+
if (!config.mcp) {
|
|
113
|
+
console.log("1Password: No MCP configuration found, skipping");
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let modified = false;
|
|
118
|
+
|
|
119
|
+
for (const [serverName, serverConfig] of Object.entries(config.mcp)) {
|
|
120
|
+
if (serverConfig?.environment) {
|
|
121
|
+
for (const [key, value] of Object.entries(serverConfig.environment)) {
|
|
122
|
+
if (typeof value === "string" && !value.startsWith("{env:") && !value.startsWith("$")) {
|
|
123
|
+
// Replace hardcoded value with env var reference
|
|
124
|
+
serverConfig.environment[key] = `{env:${key}}`;
|
|
125
|
+
modified = true;
|
|
126
|
+
console.log(`1Password: Updated opencode.json - ${serverName}.${key} -> {env:${key}}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (modified) {
|
|
133
|
+
fs.writeFileSync(OPENCODE_JSON_PATH, JSON.stringify(config, null, 2));
|
|
134
|
+
console.log("1Password: opencode.json MCP config updated to use environment variables");
|
|
135
|
+
}
|
|
136
|
+
} catch (err) {
|
|
137
|
+
console.error("1Password: Failed to update opencode.json:", err);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
58
141
|
const authenticateProviders = async (providerEnvId: string): Promise<void> => {
|
|
59
142
|
if (!opClient) return;
|
|
60
143
|
|
|
@@ -146,12 +229,15 @@ export const OnePasswordAuthPlugin: Plugin = async (ctx) => {
|
|
|
146
229
|
|
|
147
230
|
const providersEnvId = configEnvIds.OPENCODE_PROVIDERS_ENV_ID;
|
|
148
231
|
if (providersEnvId) {
|
|
232
|
+
// First update the config files to use env var references
|
|
233
|
+
await updateAuthJson(providersEnvId);
|
|
149
234
|
await authenticateProviders(providersEnvId);
|
|
150
235
|
}
|
|
151
236
|
|
|
152
237
|
const mcpEnvIdFromConfig = configEnvIds.OPENCODE_MCPS_ENV_ID;
|
|
153
238
|
if (mcpEnvIdFromConfig) {
|
|
154
239
|
mcpsEnvId = mcpEnvIdFromConfig;
|
|
240
|
+
await updateOpenCodeJsonMCP(mcpEnvIdFromConfig);
|
|
155
241
|
const toInject = await injectMCPSecrets(mcpEnvIdFromConfig);
|
|
156
242
|
if (Object.keys(toInject).length > 0) {
|
|
157
243
|
console.log(`1Password: Injected ${Object.keys(toInject).join(", ")} for MCP`);
|
package/package.json
CHANGED
package/setup.ps1
CHANGED
|
@@ -65,31 +65,53 @@ function Remove-RegistryValue {
|
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
function Get-OpenCodeNodeModulesPath {
|
|
69
|
+
$paths = @(
|
|
70
|
+
"$env:USERPROFILE\.cache\opencode\node_modules",
|
|
71
|
+
"$env:USERPROFILE\.config\opencode\node_modules",
|
|
72
|
+
"$env:APPDATA\opencode\node_modules"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
foreach ($path in $paths) {
|
|
76
|
+
if (Test-Path "$path\@1password\sdk") {
|
|
77
|
+
return $path
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return $null
|
|
81
|
+
}
|
|
82
|
+
|
|
68
83
|
function Test-1PasswordConnection {
|
|
69
84
|
param([string]$Token)
|
|
70
85
|
|
|
71
86
|
try {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
"
|
|
87
|
+
$nodeModulesPath = Get-OpenCodeNodeModulesPath
|
|
88
|
+
|
|
89
|
+
if (-not $nodeModulesPath) {
|
|
90
|
+
Write-Error "Could not find @1password/sdk in any node_modules directory"
|
|
91
|
+
return $false
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
$sdkPath = ($nodeModulesPath -replace '\\', '/') + "/@1password/sdk/dist/sdk.js"
|
|
95
|
+
|
|
96
|
+
# Create a temporary Node.js script file
|
|
97
|
+
$tempScript = [System.IO.Path]::GetTempFileName() -replace '\.tmp$', '.js'
|
|
98
|
+
|
|
99
|
+
$testScript = 'const sdk = require("' + $sdkPath + '");' + "`n" +
|
|
100
|
+
'async function test() {' + "`n" +
|
|
101
|
+
' const client = await sdk.createClient({' + "`n" +
|
|
102
|
+
' auth: "' + $Token + '",' + "`n" +
|
|
103
|
+
' integrationName: "opencode-1password-setup-test",' + "`n" +
|
|
104
|
+
' integrationVersion: "1.0.0"' + "`n" +
|
|
105
|
+
' });' + "`n" +
|
|
106
|
+
' console.log("SUCCESS");' + "`n" +
|
|
107
|
+
'}' + "`n" +
|
|
108
|
+
'test().catch(err => { console.error("FAILED:", err.message); process.exit(1); });'
|
|
109
|
+
|
|
110
|
+
$testScript | Out-File -FilePath $tempScript -Encoding UTF8 -NoNewline
|
|
111
|
+
|
|
112
|
+
$result = & node $tempScript 2>&1
|
|
113
|
+
Remove-Item $tempScript -ErrorAction SilentlyContinue
|
|
91
114
|
|
|
92
|
-
$result = node -e $testScript 2>&1
|
|
93
115
|
return $result -match "SUCCESS"
|
|
94
116
|
} catch {
|
|
95
117
|
return $false
|
|
@@ -100,36 +122,37 @@ function Get-1PasswordAudit {
|
|
|
100
122
|
param([string]$Token, [string]$ConfigEnvId)
|
|
101
123
|
|
|
102
124
|
try {
|
|
103
|
-
$
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
auth: '$Token',
|
|
109
|
-
integrationName: 'opencode-1password-setup-test',
|
|
110
|
-
integrationVersion: '1.0.0'
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
// Read bootstrap environment
|
|
114
|
-
const { variables: configVars } = await client.environments.getVariables('$ConfigEnvId');
|
|
115
|
-
|
|
116
|
-
const envIds = {};
|
|
117
|
-
for (const v of configVars) {
|
|
118
|
-
if (v.name.endsWith('_ENV_ID') && v.value) {
|
|
119
|
-
envIds[v.name] = v.value;
|
|
125
|
+
$nodeModulesPath = Get-OpenCodeNodeModulesPath
|
|
126
|
+
|
|
127
|
+
if (-not $nodeModulesPath) {
|
|
128
|
+
Write-Error "Could not find @1password/sdk in any node_modules directory"
|
|
129
|
+
return $null
|
|
120
130
|
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
console.log(JSON.stringify(envIds));
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
audit().catch(err => {
|
|
127
|
-
console.error('FAILED:', err.message);
|
|
128
|
-
process.exit(1);
|
|
129
|
-
});
|
|
130
|
-
"@
|
|
131
131
|
|
|
132
|
-
$
|
|
132
|
+
$sdkPath = ($nodeModulesPath -replace '\\', '/') + "/@1password/sdk/dist/sdk.js"
|
|
133
|
+
|
|
134
|
+
$script = 'const sdk = require("' + $sdkPath + '");' + "`n" +
|
|
135
|
+
'async function audit() {' + "`n" +
|
|
136
|
+
' const client = await sdk.createClient({' + "`n" +
|
|
137
|
+
' auth: "' + $Token + '",' + "`n" +
|
|
138
|
+
' integrationName: "opencode-1password-setup-test",' + "`n" +
|
|
139
|
+
' integrationVersion: "1.0.0"' + "`n" +
|
|
140
|
+
' });' + "`n" +
|
|
141
|
+
' const { variables: configVars } = await client.environments.getVariables("' + $ConfigEnvId + '");' + "`n" +
|
|
142
|
+
' const envIds = {};' + "`n" +
|
|
143
|
+
' for (const v of configVars) {' + "`n" +
|
|
144
|
+
' if (v.name.endsWith("_ENV_ID") && v.value) {' + "`n" +
|
|
145
|
+
' envIds[v.name] = v.value;' + "`n" +
|
|
146
|
+
' }' + "`n" +
|
|
147
|
+
' }' + "`n" +
|
|
148
|
+
' console.log(JSON.stringify(envIds));' + "`n" +
|
|
149
|
+
'}' + "`n" +
|
|
150
|
+
'audit().catch(err => { console.error("FAILED:", err.message); process.exit(1); });'
|
|
151
|
+
|
|
152
|
+
$tempScript = [System.IO.Path]::GetTempFileName() -replace '\.tmp$', '.js'
|
|
153
|
+
$script | Out-File -FilePath $tempScript -Encoding UTF8 -NoNewline
|
|
154
|
+
$jsonResult = & node $tempScript 2>&1 | Out-String
|
|
155
|
+
Remove-Item $tempScript -ErrorAction SilentlyContinue
|
|
133
156
|
return $jsonResult | ConvertFrom-Json
|
|
134
157
|
} catch {
|
|
135
158
|
return $null
|
|
@@ -139,6 +162,8 @@ audit().catch(err => {
|
|
|
139
162
|
function Show-AuditReport {
|
|
140
163
|
param([string]$Token, [hashtable]$EnvIds)
|
|
141
164
|
|
|
165
|
+
$nodeModulesPath = Get-OpenCodeNodeModulesPath
|
|
166
|
+
|
|
142
167
|
Write-Host ""
|
|
143
168
|
Write-Host "========================================" -ForegroundColor Cyan
|
|
144
169
|
Write-Host " Configuration Audit Report" -ForegroundColor Cyan
|
|
@@ -163,30 +188,27 @@ function Show-AuditReport {
|
|
|
163
188
|
Write-Host "--------------------------------"
|
|
164
189
|
|
|
165
190
|
try {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
async function read() {
|
|
170
|
-
const client = await sdk.createClient({
|
|
171
|
-
auth: '$Token',
|
|
172
|
-
integrationName:
|
|
173
|
-
integrationVersion:
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
read().catch(err => {
|
|
185
|
-
console.error('Error:', err.message);
|
|
186
|
-
});
|
|
187
|
-
"@
|
|
191
|
+
# Use forward slashes to avoid JS escape sequence issues
|
|
192
|
+
$sdkPath = ($nodeModulesPath -replace '\\', '/') + "/@1password/sdk/dist/sdk.js"
|
|
193
|
+
$script = 'const sdk = require("' + $sdkPath + '");' + "`n" +
|
|
194
|
+
'async function read() {' + "`n" +
|
|
195
|
+
' const client = await sdk.createClient({' + "`n" +
|
|
196
|
+
' auth: "' + $Token + '",' + "`n" +
|
|
197
|
+
' integrationName: "opencode-1password-setup-test",' + "`n" +
|
|
198
|
+
' integrationVersion: "1.0.0"' + "`n" +
|
|
199
|
+
' });' + "`n" +
|
|
200
|
+
' const { variables } = await client.environments.getVariables("' + $envId + '");' + "`n" +
|
|
201
|
+
' for (const v of variables) {' + "`n" +
|
|
202
|
+
' const masked = v.value ? v.value.substring(0, 8) + "••••••••" : "(empty)";' + "`n" +
|
|
203
|
+
' console.log(v.name + "=" + masked);' + "`n" +
|
|
204
|
+
' }' + "`n" +
|
|
205
|
+
'}' + "`n" +
|
|
206
|
+
'read().catch(err => { console.error("Error:", err.message); });'
|
|
188
207
|
|
|
189
|
-
$
|
|
208
|
+
$tempScript = [System.IO.Path]::GetTempFileName() -replace '\.tmp$', '.js'
|
|
209
|
+
$script | Out-File -FilePath $tempScript -Encoding UTF8 -NoNewline
|
|
210
|
+
$result = & node $tempScript 2>&1
|
|
211
|
+
Remove-Item $tempScript -ErrorAction SilentlyContinue
|
|
190
212
|
foreach ($line in $result) {
|
|
191
213
|
if ($line -and $line -notmatch "^(Error|Error:)") {
|
|
192
214
|
Write-Host " $line" -ForegroundColor Gray
|