teamcopilot 0.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/.env.example +10 -0
- package/LICENSE.md +21 -0
- package/README.md +131 -0
- package/bin/teamcopilot.js +281 -0
- package/dist/auth/index.js +189 -0
- package/dist/change-user-role.js +77 -0
- package/dist/chat/index.js +849 -0
- package/dist/constants.js +2 -0
- package/dist/create-user.js +98 -0
- package/dist/cronjob/index.js +16 -0
- package/dist/cronjob/resource-reconciliation.js +33 -0
- package/dist/delete-user.js +66 -0
- package/dist/frontend/assets/abap-CRCWOmpq.js +1 -0
- package/dist/frontend/assets/apex-DnsZk_dE.js +1 -0
- package/dist/frontend/assets/azcli-1IWB1ccx.js +1 -0
- package/dist/frontend/assets/bat-DPkNLes8.js +1 -0
- package/dist/frontend/assets/bicep-Corcdgou.js +2 -0
- package/dist/frontend/assets/cameligo-CGrWLZr3.js +1 -0
- package/dist/frontend/assets/clojure-D9WOWImG.js +1 -0
- package/dist/frontend/assets/codicon-DCmgc-ay.ttf +0 -0
- package/dist/frontend/assets/coffee-B7EJu28W.js +1 -0
- package/dist/frontend/assets/cpp-SEyurbux.js +1 -0
- package/dist/frontend/assets/csharp-BoL64M5l.js +1 -0
- package/dist/frontend/assets/csp-C46ZqvIl.js +1 -0
- package/dist/frontend/assets/css-DQU6DXDx.js +3 -0
- package/dist/frontend/assets/cssMode-BDT3WbVs.js +4 -0
- package/dist/frontend/assets/cypher-D84EuPTj.js +1 -0
- package/dist/frontend/assets/dart-D8lhlL1r.js +1 -0
- package/dist/frontend/assets/dockerfile-DLk6rpji.js +1 -0
- package/dist/frontend/assets/ecl-BO6FnfXk.js +1 -0
- package/dist/frontend/assets/editor.worker-B4pQIWZD.js +12 -0
- package/dist/frontend/assets/elixir-BRjLKONM.js +1 -0
- package/dist/frontend/assets/flow9-Cac8vKd7.js +1 -0
- package/dist/frontend/assets/freemarker2-C7-hEgID.js +3 -0
- package/dist/frontend/assets/fsharp-fd1GTHhf.js +1 -0
- package/dist/frontend/assets/go-O9LJTZXk.js +1 -0
- package/dist/frontend/assets/graphql-LQdxqEYJ.js +1 -0
- package/dist/frontend/assets/handlebars-4cwTkPir.js +1 -0
- package/dist/frontend/assets/hcl-DxDQ3s82.js +1 -0
- package/dist/frontend/assets/html-YNfE1Q0A.js +1 -0
- package/dist/frontend/assets/htmlMode-opTQ1HoB.js +4 -0
- package/dist/frontend/assets/index-DWyaVa1h.js +782 -0
- package/dist/frontend/assets/index-lXrsgeTF.css +1 -0
- package/dist/frontend/assets/ini-BvajGCUy.js +1 -0
- package/dist/frontend/assets/java-SYsfObOQ.js +1 -0
- package/dist/frontend/assets/javascript-BEwGzk7T.js +1 -0
- package/dist/frontend/assets/jsonMode-CGhIS5Al.js +10 -0
- package/dist/frontend/assets/julia-DQXNmw_w.js +1 -0
- package/dist/frontend/assets/kotlin-qQ0MG-9I.js +1 -0
- package/dist/frontend/assets/less-GGFNNJHn.js +2 -0
- package/dist/frontend/assets/lexon-Canl7DCW.js +1 -0
- package/dist/frontend/assets/liquid-QekTGCGJ.js +1 -0
- package/dist/frontend/assets/lua-D28Ae8-K.js +1 -0
- package/dist/frontend/assets/m3-DPitgjJI.js +1 -0
- package/dist/frontend/assets/markdown-B811l8j2.js +1 -0
- package/dist/frontend/assets/mdx-BAVDaB7v.js +1 -0
- package/dist/frontend/assets/mips-CdjsipkG.js +1 -0
- package/dist/frontend/assets/msdax-CYqgjx_P.js +1 -0
- package/dist/frontend/assets/mysql-BHd6q0vd.js +1 -0
- package/dist/frontend/assets/objective-c-B1aVtJYH.js +1 -0
- package/dist/frontend/assets/pascal-BhNW15KB.js +1 -0
- package/dist/frontend/assets/pascaligo-5jv8CcQD.js +1 -0
- package/dist/frontend/assets/perl-DlYyT36c.js +1 -0
- package/dist/frontend/assets/pgsql-Dy0bjov7.js +1 -0
- package/dist/frontend/assets/php-120yhfDK.js +1 -0
- package/dist/frontend/assets/pla-CjnFlu4u.js +1 -0
- package/dist/frontend/assets/postiats-CQpG440k.js +1 -0
- package/dist/frontend/assets/powerquery-DdJtto1Z.js +1 -0
- package/dist/frontend/assets/powershell-Bu_VLpJB.js +1 -0
- package/dist/frontend/assets/protobuf-IBS6jZEB.js +2 -0
- package/dist/frontend/assets/pug-kFxLfcjb.js +1 -0
- package/dist/frontend/assets/python-BQlHw7XO.js +1 -0
- package/dist/frontend/assets/qsharp-q7JyzKFN.js +1 -0
- package/dist/frontend/assets/r-BIFz-_sK.js +1 -0
- package/dist/frontend/assets/razor-Be3Wwc2E.js +1 -0
- package/dist/frontend/assets/redis-CHOsPHWR.js +1 -0
- package/dist/frontend/assets/redshift-CBifECDb.js +1 -0
- package/dist/frontend/assets/restructuredtext-CghPJEOS.js +1 -0
- package/dist/frontend/assets/ruby-CYWGW-b1.js +1 -0
- package/dist/frontend/assets/rust-DMDD0SHb.js +1 -0
- package/dist/frontend/assets/sb-BYAiYHFx.js +1 -0
- package/dist/frontend/assets/scala-Bqvq8jcR.js +1 -0
- package/dist/frontend/assets/scheme-Dhb-2j9p.js +1 -0
- package/dist/frontend/assets/scss-CTwUZ5N7.js +3 -0
- package/dist/frontend/assets/shell-CsDZo4DB.js +1 -0
- package/dist/frontend/assets/solidity-CME5AdoB.js +1 -0
- package/dist/frontend/assets/sophia-RYC1BQQz.js +1 -0
- package/dist/frontend/assets/sparql-KEyrF7De.js +1 -0
- package/dist/frontend/assets/sql-BdTr02Mf.js +1 -0
- package/dist/frontend/assets/st-C7iG7M4S.js +1 -0
- package/dist/frontend/assets/swift-D7IUmUK8.js +1 -0
- package/dist/frontend/assets/systemverilog-DgMryOEJ.js +1 -0
- package/dist/frontend/assets/tcl-PloMZuKG.js +1 -0
- package/dist/frontend/assets/tsMode-CIBFoN3z.js +11 -0
- package/dist/frontend/assets/twig-BfRIq3la.js +1 -0
- package/dist/frontend/assets/typescript-BuV9wEIE.js +1 -0
- package/dist/frontend/assets/typespec-CzxlYoT_.js +1 -0
- package/dist/frontend/assets/vb-BwAE3J76.js +1 -0
- package/dist/frontend/assets/wgsl-B_1kOXbF.js +298 -0
- package/dist/frontend/assets/xml-DcDKYaM4.js +1 -0
- package/dist/frontend/assets/yaml-CuBNmOuI.js +1 -0
- package/dist/frontend/index.html +14 -0
- package/dist/frontend/logo.svg +50 -0
- package/dist/index.js +169 -0
- package/dist/logging.js +30 -0
- package/dist/opencode-auth/index.js +122 -0
- package/dist/opencode-server.js +91 -0
- package/dist/prisma/client.js +38 -0
- package/dist/reset-password.js +73 -0
- package/dist/rotate-jwt-secret.js +20 -0
- package/dist/scripts/prisma-workspace.js +34 -0
- package/dist/skills/index.js +311 -0
- package/dist/types/permissions.js +2 -0
- package/dist/types/shared/permissions.js +17 -0
- package/dist/types/shared/skill.js +17 -0
- package/dist/types/shared/workflow-files.js +17 -0
- package/dist/types/shared/workflow.js +17 -0
- package/dist/types/skill.js +2 -0
- package/dist/types/workflow-files.js +2 -0
- package/dist/types/workflow.js +2 -0
- package/dist/users/index.js +22 -0
- package/dist/utils/approval-snapshot-common.js +596 -0
- package/dist/utils/assert.js +20 -0
- package/dist/utils/chat-session.js +44 -0
- package/dist/utils/cli-bootstrap.js +26 -0
- package/dist/utils/index.js +95 -0
- package/dist/utils/jwt-secret.js +63 -0
- package/dist/utils/opencode-auth.js +126 -0
- package/dist/utils/opencode-client.js +109 -0
- package/dist/utils/password-policy.js +12 -0
- package/dist/utils/permission-common.js +280 -0
- package/dist/utils/redact.js +108 -0
- package/dist/utils/resource-access.js +37 -0
- package/dist/utils/resource-file-routes.js +115 -0
- package/dist/utils/resource-files.js +572 -0
- package/dist/utils/runtime-paths.js +61 -0
- package/dist/utils/session-abort.js +52 -0
- package/dist/utils/skill-approval-snapshot.js +39 -0
- package/dist/utils/skill-files.js +17 -0
- package/dist/utils/skill-permissions.js +15 -0
- package/dist/utils/skill.js +217 -0
- package/dist/utils/user-role.js +14 -0
- package/dist/utils/workflow-approval-snapshot.js +38 -0
- package/dist/utils/workflow-files.js +17 -0
- package/dist/utils/workflow-interruption.js +50 -0
- package/dist/utils/workflow-permissions.js +27 -0
- package/dist/utils/workflow-runner.js +414 -0
- package/dist/utils/workflow.js +158 -0
- package/dist/utils/workspace-sync.js +204 -0
- package/dist/workflows/index.js +751 -0
- package/dist/workspace_files/.opencode/opencode.json +17 -0
- package/dist/workspace_files/.opencode/package.json +14 -0
- package/dist/workspace_files/.opencode/plugins/createSkill.ts +339 -0
- package/dist/workspace_files/.opencode/plugins/createWorkflow.ts +345 -0
- package/dist/workspace_files/.opencode/plugins/findSimilarWorkflow.ts +173 -0
- package/dist/workspace_files/.opencode/plugins/findSkill.ts +211 -0
- package/dist/workspace_files/.opencode/plugins/getSkillContent.ts +135 -0
- package/dist/workspace_files/.opencode/plugins/honeytoken-protection.ts +64 -0
- package/dist/workspace_files/.opencode/plugins/listAvailableSkills.ts +93 -0
- package/dist/workspace_files/.opencode/plugins/listAvailableWorkflows.ts +93 -0
- package/dist/workspace_files/.opencode/plugins/python-protection.ts +184 -0
- package/dist/workspace_files/.opencode/plugins/runWorkflow.ts +168 -0
- package/dist/workspace_files/.opencode/tsconfig.json +16 -0
- package/dist/workspace_files/AGENTS.md +483 -0
- package/dist/workspace_files/package-lock.json +167 -0
- package/dist/workspace_files/package.json +5 -0
- package/package.json +86 -0
- package/prisma/migrations/20260203040755_init/migration.sql +20 -0
- package/prisma/migrations/20260204034845_replace_google_auth_with_email_password/migration.sql +25 -0
- package/prisma/migrations/20260207022226_add_user_role/migration.sql +25 -0
- package/prisma/migrations/20260210161254_add_workflow_runs/migration.sql +16 -0
- package/prisma/migrations/20260211050606_adds_workflow_table/migration.sql +40 -0
- package/prisma/migrations/20260211050750_adds_fkey_constraint/migration.sql +21 -0
- package/prisma/migrations/20260211051912_removes_workflow_table/migration.sql +34 -0
- package/prisma/migrations/20260211052238_changes_workflow_id_to_slug/migration.sql +27 -0
- package/prisma/migrations/20260212051912_add_output_to_workflow_runs/migration.sql +2 -0
- package/prisma/migrations/20260213073006_add_chat_sessions/migration.sql +13 -0
- package/prisma/migrations/20260216053202_add_chat_sessions_opencode_session_id_idx/migration.sql +2 -0
- package/prisma/migrations/20260216053237_drop_redundant_chat_sessions_opencode_idx/migration.sql +2 -0
- package/prisma/migrations/20260219060705_makes/migration.sql +24 -0
- package/prisma/migrations/20260222040542_add_workflow_execution_permissions/migration.sql +18 -0
- package/prisma/migrations/20260222040815_remove_workflow_execution_permissions/migration.sql +10 -0
- package/prisma/migrations/20260222041348_add_workflow_execution_permissions_final/migration.sql +17 -0
- package/prisma/migrations/20260222041741_rename_to_tool_execution_permissions/migration.sql +30 -0
- package/prisma/migrations/20260222041826_simplify_tool_execution_permissions/migration.sql +29 -0
- package/prisma/migrations/20260222041950_add_fields_for_standalone_permissions/migration.sql +32 -0
- package/prisma/migrations/20260222042954_simplify_tool_permissions_table/migration.sql +27 -0
- package/prisma/migrations/20260223073902_add_workflow_run_permissions_tables/migration.sql +23 -0
- package/prisma/migrations/20260225025151_add_workflow_metadata/migration.sql +16 -0
- package/prisma/migrations/20260225031035_merge_workflow_permissions_into_metadata/migration.sql +44 -0
- package/prisma/migrations/20260225031752_removes_default_for_run_permission_mode/migration.sql +20 -0
- package/prisma/migrations/20260225033603_remove_workflow_metadata_user_fkeys/migration.sql +18 -0
- package/prisma/migrations/20260225043032_restore_workflow_metadata_user_fkeys/migration.sql +20 -0
- package/prisma/migrations/20260225091423_add_workflow_approved_snapshots/migration.sql +28 -0
- package/prisma/migrations/20260226032121_add_is_approved_to_workflow_metadata/migration.sql +21 -0
- package/prisma/migrations/20260226032444_undoes_last_db_change/migration.sql +26 -0
- package/prisma/migrations/20260227120000_remove_snapshot_hash_from_approved_snapshots/migration.sql +16 -0
- package/prisma/migrations/20260228071125_adds_workspace_path_to_snapshot_table/migration.sql +22 -0
- package/prisma/migrations/20260228071217_modifies_index_and_removes_default_value/migration.sql +22 -0
- package/prisma/migrations/20260228071710_undoes_previous/migration.sql +27 -0
- package/prisma/migrations/20260228105022_add_must_change_password_first_login/migration.sql +20 -0
- package/prisma/migrations/20260301115439_add_workflow_run_log_refs/migration.sql +8 -0
- package/prisma/migrations/20260301122557_add_workflow_aborted_sessions/migration.sql +5 -0
- package/prisma/migrations/20260302045545_move_workflow_run_log_refs_into_workflow_runs/migration.sql +17 -0
- package/prisma/migrations/20260303040318_add_skill_tables/migration.sql +61 -0
- package/prisma/migrations/20260303051533_unify_resource_permissions/migration.sql +97 -0
- package/prisma/migrations/20260303064255_unify_resource_metadata_and_snapshots/migration.sql +179 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +147 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.apiHandler = apiHandler;
|
|
7
|
+
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
8
|
+
const client_1 = __importDefault(require("../prisma/client"));
|
|
9
|
+
const assert_1 = require("./assert");
|
|
10
|
+
const jwt_secret_1 = require("./jwt-secret");
|
|
11
|
+
function apiHandler(handler, requireAuth) {
|
|
12
|
+
return async (req, res, next) => {
|
|
13
|
+
try {
|
|
14
|
+
const authHeader = req.headers['authorization'];
|
|
15
|
+
if (!authHeader && requireAuth) {
|
|
16
|
+
throw {
|
|
17
|
+
status: 401,
|
|
18
|
+
message: 'Missing authorization header. Please pass an authorization bearer token in the header.'
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
if (authHeader) {
|
|
22
|
+
const rawToken = authHeader.split(' ')[1];
|
|
23
|
+
(0, assert_1.assertCondition)(rawToken, 'Missing authorization bearer token');
|
|
24
|
+
try {
|
|
25
|
+
const decoded = jsonwebtoken_1.default.verify(rawToken, (0, jwt_secret_1.getJwtSecret)());
|
|
26
|
+
const payload = decoded;
|
|
27
|
+
(0, assert_1.assertCondition)(typeof payload.sub === "string" && payload.sub.length > 0, "Invalid authorization token subject");
|
|
28
|
+
const user = await client_1.default.users.findUnique({
|
|
29
|
+
where: {
|
|
30
|
+
id: payload.sub
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
if (!user) {
|
|
34
|
+
throw {
|
|
35
|
+
status: 401,
|
|
36
|
+
message: 'Invalid authorization token. Please pass a valid authorization bearer token in the header.'
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
req.userId = user.id;
|
|
40
|
+
req.email = user.email;
|
|
41
|
+
req.name = user.name;
|
|
42
|
+
req.role = user.role;
|
|
43
|
+
req.opencode_session_id = undefined;
|
|
44
|
+
req.tokenUse = payload.token_use === "password_change" ? "password_change" : "access";
|
|
45
|
+
req.mustChangePassword = user.must_change_password;
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
if (e instanceof jsonwebtoken_1.default.JsonWebTokenError || e instanceof jsonwebtoken_1.default.TokenExpiredError) {
|
|
49
|
+
const session = await client_1.default.chat_sessions.findFirst({
|
|
50
|
+
where: { opencode_session_id: rawToken },
|
|
51
|
+
include: {
|
|
52
|
+
user: true
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
if (session) {
|
|
56
|
+
req.userId = session.user.id;
|
|
57
|
+
req.email = session.user.email;
|
|
58
|
+
req.name = session.user.name;
|
|
59
|
+
req.role = session.user.role;
|
|
60
|
+
req.opencode_session_id = session.opencode_session_id;
|
|
61
|
+
req.tokenUse = "access";
|
|
62
|
+
req.mustChangePassword = session.user.must_change_password;
|
|
63
|
+
res.locals.skipResponseSanitization = true;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
throw e;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (requireAuth && !req.userId) {
|
|
72
|
+
throw {
|
|
73
|
+
status: 401,
|
|
74
|
+
message: 'Invalid authorization token. Please pass a valid authorization bearer token in the header.'
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
if (requireAuth && req.tokenUse === "password_change") {
|
|
78
|
+
throw {
|
|
79
|
+
status: 401,
|
|
80
|
+
message: 'This token can only be used to complete password change.'
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
if (requireAuth && req.tokenUse === "access" && req.mustChangePassword) {
|
|
84
|
+
throw {
|
|
85
|
+
status: 401,
|
|
86
|
+
message: "Password change is required before using this access token."
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
await handler(req, res, next);
|
|
90
|
+
}
|
|
91
|
+
catch (e) {
|
|
92
|
+
next(e);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loadJwtSecret = loadJwtSecret;
|
|
7
|
+
exports.getJwtSecret = getJwtSecret;
|
|
8
|
+
exports.rotateJwtSecret = rotateJwtSecret;
|
|
9
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
10
|
+
const client_1 = __importDefault(require("../prisma/client"));
|
|
11
|
+
const assert_1 = require("./assert");
|
|
12
|
+
const AUTH_TOKEN_SECRET_KEY = "jwt_secret";
|
|
13
|
+
let cachedJwtSecret = null;
|
|
14
|
+
function generateJwtSecret() {
|
|
15
|
+
return node_crypto_1.default.randomBytes(48).toString("hex");
|
|
16
|
+
}
|
|
17
|
+
async function ensureJwtSecret() {
|
|
18
|
+
const existing = await client_1.default.key_value.findUnique({
|
|
19
|
+
where: { key: AUTH_TOKEN_SECRET_KEY }
|
|
20
|
+
});
|
|
21
|
+
if (existing?.value) {
|
|
22
|
+
cachedJwtSecret = existing.value;
|
|
23
|
+
return existing.value;
|
|
24
|
+
}
|
|
25
|
+
const generated = generateJwtSecret();
|
|
26
|
+
try {
|
|
27
|
+
await client_1.default.key_value.create({
|
|
28
|
+
data: {
|
|
29
|
+
key: AUTH_TOKEN_SECRET_KEY,
|
|
30
|
+
value: generated
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
cachedJwtSecret = generated;
|
|
34
|
+
return generated;
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
const fallback = await client_1.default.key_value.findUnique({
|
|
38
|
+
where: { key: AUTH_TOKEN_SECRET_KEY }
|
|
39
|
+
});
|
|
40
|
+
if (fallback?.value) {
|
|
41
|
+
cachedJwtSecret = fallback.value;
|
|
42
|
+
return fallback.value;
|
|
43
|
+
}
|
|
44
|
+
throw err;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function loadJwtSecret() {
|
|
48
|
+
await ensureJwtSecret();
|
|
49
|
+
}
|
|
50
|
+
function getJwtSecret() {
|
|
51
|
+
(0, assert_1.assertCondition)(typeof cachedJwtSecret === "string" && cachedJwtSecret.length > 0, "JWT secret not initialized");
|
|
52
|
+
return cachedJwtSecret;
|
|
53
|
+
}
|
|
54
|
+
async function rotateJwtSecret() {
|
|
55
|
+
const nextSecret = generateJwtSecret();
|
|
56
|
+
await client_1.default.key_value.upsert({
|
|
57
|
+
where: { key: AUTH_TOKEN_SECRET_KEY },
|
|
58
|
+
create: { key: AUTH_TOKEN_SECRET_KEY, value: nextSecret },
|
|
59
|
+
update: { value: nextSecret }
|
|
60
|
+
});
|
|
61
|
+
cachedJwtSecret = nextSecret;
|
|
62
|
+
return nextSecret;
|
|
63
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getConfiguredModelProviderId = getConfiguredModelProviderId;
|
|
7
|
+
exports.initializeOpencodeAuthStorage = initializeOpencodeAuthStorage;
|
|
8
|
+
exports.getRuntimeProviderAuth = getRuntimeProviderAuth;
|
|
9
|
+
exports.setRuntimeProviderAuth = setRuntimeProviderAuth;
|
|
10
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const assert_1 = require("./assert");
|
|
13
|
+
const workspace_sync_1 = require("./workspace-sync");
|
|
14
|
+
const WORKSPACE_OPENCODE_DIR = ".opencode";
|
|
15
|
+
const WORKSPACE_AUTH_FILE = "auth.json";
|
|
16
|
+
const RUNTIME_DATA_HOME_DIR = "xdg-data";
|
|
17
|
+
function getWorkspaceOpencodeDir() {
|
|
18
|
+
return path_1.default.join((0, workspace_sync_1.getWorkspaceDirFromEnv)(), WORKSPACE_OPENCODE_DIR);
|
|
19
|
+
}
|
|
20
|
+
function getRuntimeDataHomePath() {
|
|
21
|
+
return path_1.default.join(getWorkspaceOpencodeDir(), RUNTIME_DATA_HOME_DIR);
|
|
22
|
+
}
|
|
23
|
+
function getRuntimeAuthPath() {
|
|
24
|
+
return path_1.default.join(getRuntimeDataHomePath(), "opencode", WORKSPACE_AUTH_FILE);
|
|
25
|
+
}
|
|
26
|
+
function normalizeProviderId(providerId) {
|
|
27
|
+
return providerId.replace(/\/+$/, "");
|
|
28
|
+
}
|
|
29
|
+
function isNonEmptyString(value) {
|
|
30
|
+
return typeof value === "string" && value.length > 0;
|
|
31
|
+
}
|
|
32
|
+
function isValidProviderAuthInfo(value) {
|
|
33
|
+
if (!value || typeof value !== "object") {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
const candidate = value;
|
|
37
|
+
if (candidate.type === "api") {
|
|
38
|
+
return isNonEmptyString(candidate.key);
|
|
39
|
+
}
|
|
40
|
+
if (candidate.type === "oauth") {
|
|
41
|
+
const hasOptionalAccountId = candidate.accountId === undefined || isNonEmptyString(candidate.accountId);
|
|
42
|
+
const hasOptionalEnterpriseUrl = candidate.enterpriseUrl === undefined || isNonEmptyString(candidate.enterpriseUrl);
|
|
43
|
+
return (isNonEmptyString(candidate.refresh)
|
|
44
|
+
&& isNonEmptyString(candidate.access)
|
|
45
|
+
&& typeof candidate.expires === "number"
|
|
46
|
+
&& Number.isFinite(candidate.expires)
|
|
47
|
+
&& hasOptionalAccountId
|
|
48
|
+
&& hasOptionalEnterpriseUrl);
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
async function readAuthRecord(filepath) {
|
|
53
|
+
try {
|
|
54
|
+
const content = await promises_1.default.readFile(filepath, "utf-8");
|
|
55
|
+
const parsed = JSON.parse(content);
|
|
56
|
+
const entries = {};
|
|
57
|
+
for (const [providerId, info] of Object.entries(parsed)) {
|
|
58
|
+
if (!isValidProviderAuthInfo(info)) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
entries[providerId] = info;
|
|
62
|
+
}
|
|
63
|
+
return entries;
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
const nodeError = err;
|
|
67
|
+
if (nodeError.code === "ENOENT") {
|
|
68
|
+
return {};
|
|
69
|
+
}
|
|
70
|
+
throw err;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async function writeAuthRecord(filepath, data) {
|
|
74
|
+
await promises_1.default.mkdir(path_1.default.dirname(filepath), { recursive: true });
|
|
75
|
+
const tempPath = `${filepath}.tmp-${process.pid}-${Date.now()}`;
|
|
76
|
+
await promises_1.default.writeFile(tempPath, `${JSON.stringify(data, null, 2)}\n`, {
|
|
77
|
+
encoding: "utf-8",
|
|
78
|
+
mode: 0o600,
|
|
79
|
+
});
|
|
80
|
+
await promises_1.default.rename(tempPath, filepath);
|
|
81
|
+
await promises_1.default.chmod(filepath, 0o600).catch(() => { });
|
|
82
|
+
}
|
|
83
|
+
function getConfiguredModelProviderId() {
|
|
84
|
+
const model = (0, assert_1.assertEnv)("OPENCODE_MODEL");
|
|
85
|
+
const [providerId, ...parts] = model.split("/");
|
|
86
|
+
if (!providerId || parts.length === 0) {
|
|
87
|
+
throw new Error("OPENCODE_MODEL must be in the format <provider>/<model>");
|
|
88
|
+
}
|
|
89
|
+
return providerId;
|
|
90
|
+
}
|
|
91
|
+
function configureOpencodeDataHome() {
|
|
92
|
+
const runtimeDataHome = getRuntimeDataHomePath();
|
|
93
|
+
process.env.XDG_DATA_HOME = runtimeDataHome;
|
|
94
|
+
return runtimeDataHome;
|
|
95
|
+
}
|
|
96
|
+
async function initializeOpencodeAuthStorage() {
|
|
97
|
+
configureOpencodeDataHome();
|
|
98
|
+
await promises_1.default.mkdir(getWorkspaceOpencodeDir(), { recursive: true });
|
|
99
|
+
const runtimeAuthPath = getRuntimeAuthPath();
|
|
100
|
+
try {
|
|
101
|
+
await promises_1.default.access(runtimeAuthPath);
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
const nodeError = err;
|
|
105
|
+
if (nodeError.code !== "ENOENT") {
|
|
106
|
+
throw err;
|
|
107
|
+
}
|
|
108
|
+
await writeAuthRecord(runtimeAuthPath, {});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function getAuthForProvider(record, providerId) {
|
|
112
|
+
const normalizedProviderId = normalizeProviderId(providerId);
|
|
113
|
+
return record[providerId] || record[normalizedProviderId] || record[`${normalizedProviderId}/`];
|
|
114
|
+
}
|
|
115
|
+
async function getRuntimeProviderAuth(providerId) {
|
|
116
|
+
const record = await readAuthRecord(getRuntimeAuthPath());
|
|
117
|
+
return getAuthForProvider(record, providerId);
|
|
118
|
+
}
|
|
119
|
+
async function setRuntimeProviderAuth(providerId, info) {
|
|
120
|
+
const normalizedProviderId = normalizeProviderId(providerId);
|
|
121
|
+
const runtimeRecord = await readAuthRecord(getRuntimeAuthPath());
|
|
122
|
+
delete runtimeRecord[providerId];
|
|
123
|
+
delete runtimeRecord[`${normalizedProviderId}/`];
|
|
124
|
+
runtimeRecord[normalizedProviderId] = info;
|
|
125
|
+
await writeAuthRecord(getRuntimeAuthPath(), runtimeRecord);
|
|
126
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getOpencodeClient = getOpencodeClient;
|
|
7
|
+
exports.getOpencodePort = getOpencodePort;
|
|
8
|
+
exports.getWorkspaceDir = getWorkspaceDir;
|
|
9
|
+
exports.getPendingQuestionForSession = getPendingQuestionForSession;
|
|
10
|
+
exports.listPendingPermissionsForSession = listPendingPermissionsForSession;
|
|
11
|
+
exports.listPendingPermissions = listPendingPermissions;
|
|
12
|
+
exports.replyToPendingQuestion = replyToPendingQuestion;
|
|
13
|
+
exports.replyToPendingPermission = replyToPendingPermission;
|
|
14
|
+
const path_1 = __importDefault(require("path"));
|
|
15
|
+
const assert_1 = require("./assert");
|
|
16
|
+
// Use dynamic import for ESM-only SDK
|
|
17
|
+
let _createOpencodeClient = null;
|
|
18
|
+
async function loadSdk() {
|
|
19
|
+
if (!_createOpencodeClient) {
|
|
20
|
+
const sdk = await import("@opencode-ai/sdk");
|
|
21
|
+
_createOpencodeClient = sdk.createOpencodeClient;
|
|
22
|
+
}
|
|
23
|
+
return _createOpencodeClient;
|
|
24
|
+
}
|
|
25
|
+
async function getOpencodeClient() {
|
|
26
|
+
const createOpencodeClient = await loadSdk();
|
|
27
|
+
const port = (0, assert_1.parseIntStrict)((0, assert_1.assertEnv)("OPENCODE_PORT"), "OPENCODE_PORT");
|
|
28
|
+
// Get workspace directory from env, resolve relative paths to absolute
|
|
29
|
+
let workspaceDir = (0, assert_1.assertEnv)("WORKSPACE_DIR");
|
|
30
|
+
if (!path_1.default.isAbsolute(workspaceDir)) {
|
|
31
|
+
workspaceDir = path_1.default.resolve(process.cwd(), workspaceDir);
|
|
32
|
+
}
|
|
33
|
+
return createOpencodeClient({
|
|
34
|
+
baseUrl: `http://localhost:${port}`,
|
|
35
|
+
directory: workspaceDir
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function getOpencodePort() {
|
|
39
|
+
return (0, assert_1.parseIntStrict)((0, assert_1.assertEnv)("OPENCODE_PORT"), "OPENCODE_PORT");
|
|
40
|
+
}
|
|
41
|
+
function getOpencodeBaseUrl() {
|
|
42
|
+
return `http://localhost:${getOpencodePort()}`;
|
|
43
|
+
}
|
|
44
|
+
function getWorkspaceDir() {
|
|
45
|
+
let workspaceDir = (0, assert_1.assertEnv)("WORKSPACE_DIR");
|
|
46
|
+
if (!path_1.default.isAbsolute(workspaceDir)) {
|
|
47
|
+
workspaceDir = path_1.default.resolve(process.cwd(), workspaceDir);
|
|
48
|
+
}
|
|
49
|
+
return workspaceDir;
|
|
50
|
+
}
|
|
51
|
+
async function getPendingQuestionForSession(opencodeSessionId) {
|
|
52
|
+
const workspaceDir = getWorkspaceDir();
|
|
53
|
+
const response = await fetch(`${getOpencodeBaseUrl()}/question?directory=${encodeURIComponent(workspaceDir)}`);
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
const errorText = await response.text();
|
|
56
|
+
throw new Error(`Failed to list pending questions: ${errorText}`);
|
|
57
|
+
}
|
|
58
|
+
const questions = await response.json();
|
|
59
|
+
(0, assert_1.assertCondition)(Array.isArray(questions), "Pending question response is not an array");
|
|
60
|
+
const match = questions.find((question) => question.sessionID === opencodeSessionId);
|
|
61
|
+
return match ?? null;
|
|
62
|
+
}
|
|
63
|
+
async function listPendingPermissionsForSession(opencodeSessionId) {
|
|
64
|
+
const permissions = await listPendingPermissions();
|
|
65
|
+
const match = permissions.filter((permission) => permission.sessionID === opencodeSessionId);
|
|
66
|
+
return match;
|
|
67
|
+
}
|
|
68
|
+
async function listPendingPermissions() {
|
|
69
|
+
const workspaceDir = getWorkspaceDir();
|
|
70
|
+
const response = await fetch(`${getOpencodeBaseUrl()}/permission?directory=${encodeURIComponent(workspaceDir)}`);
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
const errorText = await response.text();
|
|
73
|
+
throw new Error(`Failed to list pending permissions: ${errorText}`);
|
|
74
|
+
}
|
|
75
|
+
const permissions = await response.json();
|
|
76
|
+
(0, assert_1.assertCondition)(Array.isArray(permissions), "Pending permission response is not an array");
|
|
77
|
+
return permissions;
|
|
78
|
+
}
|
|
79
|
+
async function replyToPendingQuestion(questionId, answers) {
|
|
80
|
+
const workspaceDir = getWorkspaceDir();
|
|
81
|
+
const response = await fetch(`${getOpencodeBaseUrl()}/question/${encodeURIComponent(questionId)}/reply?directory=${encodeURIComponent(workspaceDir)}`, {
|
|
82
|
+
method: 'POST',
|
|
83
|
+
headers: {
|
|
84
|
+
'Content-Type': 'application/json'
|
|
85
|
+
},
|
|
86
|
+
body: JSON.stringify({ answers })
|
|
87
|
+
});
|
|
88
|
+
if (!response.ok) {
|
|
89
|
+
const errorText = await response.text();
|
|
90
|
+
throw new Error(`Failed to reply to pending question: ${errorText}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async function replyToPendingPermission(_opencodeSessionId, permissionId, response) {
|
|
94
|
+
const workspaceDir = getWorkspaceDir();
|
|
95
|
+
const replyResponse = await fetch(`${getOpencodeBaseUrl()}/permission/${encodeURIComponent(permissionId)}/reply?directory=${encodeURIComponent(workspaceDir)}`, {
|
|
96
|
+
method: 'POST',
|
|
97
|
+
headers: {
|
|
98
|
+
'Content-Type': 'application/json'
|
|
99
|
+
},
|
|
100
|
+
body: JSON.stringify({
|
|
101
|
+
reply: response,
|
|
102
|
+
message: response === "reject" ? "User rejected permission request" : "User approved permission request"
|
|
103
|
+
})
|
|
104
|
+
});
|
|
105
|
+
if (!replyResponse.ok) {
|
|
106
|
+
const errorText = await replyResponse.text();
|
|
107
|
+
throw new Error(`Failed to reply to pending permission: ${errorText}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MIN_PASSWORD_LENGTH = void 0;
|
|
4
|
+
exports.isPasswordValid = isPasswordValid;
|
|
5
|
+
exports.getPasswordPolicyErrorMessage = getPasswordPolicyErrorMessage;
|
|
6
|
+
exports.MIN_PASSWORD_LENGTH = 8;
|
|
7
|
+
function isPasswordValid(password) {
|
|
8
|
+
return password.length >= exports.MIN_PASSWORD_LENGTH;
|
|
9
|
+
}
|
|
10
|
+
function getPasswordPolicyErrorMessage() {
|
|
11
|
+
return `Password must be at least ${exports.MIN_PASSWORD_LENGTH} characters`;
|
|
12
|
+
}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.assertCommonPermissionMode = assertCommonPermissionMode;
|
|
7
|
+
exports.getResourcePermissionWithUsers = getResourcePermissionWithUsers;
|
|
8
|
+
exports.ensureResourcePermissions = ensureResourcePermissions;
|
|
9
|
+
exports.initializeResourcePermissionsForCreator = initializeResourcePermissionsForCreator;
|
|
10
|
+
exports.addUserToResourcePermissionsIfRestricted = addUserToResourcePermissionsIfRestricted;
|
|
11
|
+
exports.setResourcePermissions = setResourcePermissions;
|
|
12
|
+
const client_1 = __importDefault(require("../prisma/client"));
|
|
13
|
+
const client_2 = require("@prisma/client");
|
|
14
|
+
function assertCommonPermissionMode(mode, label) {
|
|
15
|
+
if (mode !== "restricted" && mode !== "everyone") {
|
|
16
|
+
throw {
|
|
17
|
+
status: 500,
|
|
18
|
+
message: `Invalid ${label} permission mode: ${mode}`
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
return mode;
|
|
22
|
+
}
|
|
23
|
+
async function getExistingUserIds(userIds) {
|
|
24
|
+
if (userIds.length === 0)
|
|
25
|
+
return [];
|
|
26
|
+
const users = await client_1.default.users.findMany({
|
|
27
|
+
where: { id: { in: userIds } },
|
|
28
|
+
select: { id: true }
|
|
29
|
+
});
|
|
30
|
+
return users.map((user) => user.id);
|
|
31
|
+
}
|
|
32
|
+
async function resolveRestrictedPermissionUserIds(allowedUserIds, ownerUserId) {
|
|
33
|
+
let dedupedUserIds = Array.from(new Set(allowedUserIds));
|
|
34
|
+
if (ownerUserId) {
|
|
35
|
+
const ownerExists = (await getExistingUserIds([ownerUserId])).length === 1;
|
|
36
|
+
if (ownerExists && !dedupedUserIds.includes(ownerUserId)) {
|
|
37
|
+
dedupedUserIds = [...dedupedUserIds, ownerUserId];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (dedupedUserIds.length === 0) {
|
|
41
|
+
throw {
|
|
42
|
+
status: 400,
|
|
43
|
+
message: "restricted permissions require at least one allowed user"
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const existingUserIds = await getExistingUserIds(dedupedUserIds);
|
|
47
|
+
if (existingUserIds.length !== dedupedUserIds.length) {
|
|
48
|
+
throw {
|
|
49
|
+
status: 400,
|
|
50
|
+
message: "One or more selected users do not exist"
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return existingUserIds;
|
|
54
|
+
}
|
|
55
|
+
async function getResourcePermissionWithUsers(resourceKind, slug, notFoundLabel) {
|
|
56
|
+
const permission = await client_1.default.resource_permissions.findUnique({
|
|
57
|
+
where: {
|
|
58
|
+
resource_kind_resource_slug: {
|
|
59
|
+
resource_kind: resourceKind,
|
|
60
|
+
resource_slug: slug,
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
include: {
|
|
64
|
+
allowedUsers: {
|
|
65
|
+
include: {
|
|
66
|
+
user: {
|
|
67
|
+
select: { id: true, name: true, email: true }
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
orderBy: { created_at: "asc" }
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
if (!permission) {
|
|
75
|
+
throw {
|
|
76
|
+
status: 500,
|
|
77
|
+
message: `${notFoundLabel} permissions missing for ${notFoundLabel.toLowerCase()}: ${slug}`
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return permission;
|
|
81
|
+
}
|
|
82
|
+
async function ensureResourcePermissions(resourceKind, slug, candidateUserIds) {
|
|
83
|
+
const existing = await client_1.default.resource_permissions.findUnique({
|
|
84
|
+
where: {
|
|
85
|
+
resource_kind_resource_slug: {
|
|
86
|
+
resource_kind: resourceKind,
|
|
87
|
+
resource_slug: slug,
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
select: { resource_slug: true }
|
|
91
|
+
});
|
|
92
|
+
if (existing)
|
|
93
|
+
return;
|
|
94
|
+
const existingUserIds = await getExistingUserIds(Array.from(new Set(candidateUserIds)));
|
|
95
|
+
const now = BigInt(Date.now());
|
|
96
|
+
try {
|
|
97
|
+
await client_1.default.resource_permissions.create({
|
|
98
|
+
data: {
|
|
99
|
+
resource_kind: resourceKind,
|
|
100
|
+
resource_slug: slug,
|
|
101
|
+
permission_mode: "restricted",
|
|
102
|
+
created_at: now,
|
|
103
|
+
updated_at: now,
|
|
104
|
+
allowedUsers: existingUserIds.length > 0 ? {
|
|
105
|
+
createMany: {
|
|
106
|
+
data: existingUserIds.map((userId) => ({
|
|
107
|
+
user_id: userId,
|
|
108
|
+
created_at: now
|
|
109
|
+
}))
|
|
110
|
+
}
|
|
111
|
+
} : undefined
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
if (err instanceof client_2.Prisma.PrismaClientKnownRequestError && err.code === "P2002") {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
throw err;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async function initializeResourcePermissionsForCreator(resourceKind, slug, creatorUserId) {
|
|
123
|
+
const existing = await client_1.default.resource_permissions.findUnique({
|
|
124
|
+
where: {
|
|
125
|
+
resource_kind_resource_slug: {
|
|
126
|
+
resource_kind: resourceKind,
|
|
127
|
+
resource_slug: slug,
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
select: { permission_mode: true }
|
|
131
|
+
});
|
|
132
|
+
const now = BigInt(Date.now());
|
|
133
|
+
if (!existing) {
|
|
134
|
+
await client_1.default.resource_permissions.create({
|
|
135
|
+
data: {
|
|
136
|
+
resource_kind: resourceKind,
|
|
137
|
+
resource_slug: slug,
|
|
138
|
+
permission_mode: "restricted",
|
|
139
|
+
created_at: now,
|
|
140
|
+
updated_at: now,
|
|
141
|
+
allowedUsers: {
|
|
142
|
+
create: {
|
|
143
|
+
user_id: creatorUserId,
|
|
144
|
+
created_at: now
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (existing.permission_mode === "restricted") {
|
|
152
|
+
await client_1.default.resource_permissions.update({
|
|
153
|
+
where: {
|
|
154
|
+
resource_kind_resource_slug: {
|
|
155
|
+
resource_kind: resourceKind,
|
|
156
|
+
resource_slug: slug,
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
data: {
|
|
160
|
+
updated_at: now,
|
|
161
|
+
allowedUsers: {
|
|
162
|
+
connectOrCreate: {
|
|
163
|
+
where: {
|
|
164
|
+
resource_kind_resource_slug_user_id: {
|
|
165
|
+
resource_kind: resourceKind,
|
|
166
|
+
resource_slug: slug,
|
|
167
|
+
user_id: creatorUserId,
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
create: {
|
|
171
|
+
user_id: creatorUserId,
|
|
172
|
+
created_at: now
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
async function addUserToResourcePermissionsIfRestricted(resourceKind, slug, userId, ownerUserId) {
|
|
181
|
+
const permission = await client_1.default.resource_permissions.findUnique({
|
|
182
|
+
where: {
|
|
183
|
+
resource_kind_resource_slug: {
|
|
184
|
+
resource_kind: resourceKind,
|
|
185
|
+
resource_slug: slug,
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
select: { permission_mode: true }
|
|
189
|
+
});
|
|
190
|
+
if (!permission) {
|
|
191
|
+
await ensureResourcePermissions(resourceKind, slug, [ownerUserId, userId].filter((id) => Boolean(id)));
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (permission.permission_mode !== "restricted") {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const now = BigInt(Date.now());
|
|
198
|
+
await client_1.default.resource_permissions.update({
|
|
199
|
+
where: {
|
|
200
|
+
resource_kind_resource_slug: {
|
|
201
|
+
resource_kind: resourceKind,
|
|
202
|
+
resource_slug: slug,
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
data: {
|
|
206
|
+
updated_at: now,
|
|
207
|
+
allowedUsers: {
|
|
208
|
+
connectOrCreate: {
|
|
209
|
+
where: {
|
|
210
|
+
resource_kind_resource_slug_user_id: {
|
|
211
|
+
resource_kind: resourceKind,
|
|
212
|
+
resource_slug: slug,
|
|
213
|
+
user_id: userId,
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
create: {
|
|
217
|
+
user_id: userId,
|
|
218
|
+
created_at: now
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
async function setResourcePermissions(resourceKind, slug, payload, ownerUserId) {
|
|
226
|
+
const now = BigInt(Date.now());
|
|
227
|
+
if (payload.mode === "everyone") {
|
|
228
|
+
await client_1.default.$transaction(async (tx) => {
|
|
229
|
+
await tx.resource_permissions.update({
|
|
230
|
+
where: {
|
|
231
|
+
resource_kind_resource_slug: {
|
|
232
|
+
resource_kind: resourceKind,
|
|
233
|
+
resource_slug: slug,
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
data: {
|
|
237
|
+
permission_mode: "everyone",
|
|
238
|
+
updated_at: now
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
await tx.resource_permission_users.deleteMany({
|
|
242
|
+
where: {
|
|
243
|
+
resource_kind: resourceKind,
|
|
244
|
+
resource_slug: slug,
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
return getResourcePermissionWithUsers(resourceKind, slug, resourceKind === "workflow" ? "Workflow run" : "Skill access");
|
|
249
|
+
}
|
|
250
|
+
const existingUserIds = await resolveRestrictedPermissionUserIds(payload.allowed_user_ids, ownerUserId);
|
|
251
|
+
await client_1.default.$transaction(async (tx) => {
|
|
252
|
+
await tx.resource_permissions.update({
|
|
253
|
+
where: {
|
|
254
|
+
resource_kind_resource_slug: {
|
|
255
|
+
resource_kind: resourceKind,
|
|
256
|
+
resource_slug: slug,
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
data: {
|
|
260
|
+
permission_mode: "restricted",
|
|
261
|
+
updated_at: now
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
await tx.resource_permission_users.deleteMany({
|
|
265
|
+
where: {
|
|
266
|
+
resource_kind: resourceKind,
|
|
267
|
+
resource_slug: slug,
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
await tx.resource_permission_users.createMany({
|
|
271
|
+
data: existingUserIds.map((userId) => ({
|
|
272
|
+
resource_kind: resourceKind,
|
|
273
|
+
resource_slug: slug,
|
|
274
|
+
user_id: userId,
|
|
275
|
+
created_at: now
|
|
276
|
+
}))
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
return getResourcePermissionWithUsers(resourceKind, slug, resourceKind === "workflow" ? "Workflow run" : "Skill access");
|
|
280
|
+
}
|