@cipherstash/stack 0.14.0 → 0.15.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/CHANGELOG.md +6 -0
- package/dist/drizzle/index.cjs +4 -2
- package/dist/drizzle/index.cjs.map +1 -1
- package/dist/drizzle/index.js +4 -2
- package/dist/drizzle/index.js.map +1 -1
- package/dist/identity/index.js +63 -3
- package/dist/identity/index.js.map +1 -1
- package/dist/index.cjs +6 -365
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -5
- package/package.json +1 -1
- package/dist/chunk-GXGEW6T4.js +0 -68
- package/dist/chunk-GXGEW6T4.js.map +0 -1
- package/dist/chunk-XTKLRDGJ.js +0 -357
- package/dist/chunk-XTKLRDGJ.js.map +0 -1
- package/dist/secrets/index.cjs +0 -3143
- package/dist/secrets/index.cjs.map +0 -1
- package/dist/secrets/index.d.cts +0 -225
- package/dist/secrets/index.d.ts +0 -225
- package/dist/secrets/index.js +0 -14
- package/dist/secrets/index.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
2
|
var __defProp = Object.defineProperty;
|
|
4
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
6
|
var __export = (target, all) => {
|
|
9
7
|
for (var name in all)
|
|
@@ -17,21 +15,12 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
15
|
}
|
|
18
16
|
return to;
|
|
19
17
|
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
19
|
|
|
30
20
|
// src/index.ts
|
|
31
21
|
var index_exports = {};
|
|
32
22
|
__export(index_exports, {
|
|
33
23
|
Encryption: () => Encryption,
|
|
34
|
-
Secrets: () => Secrets,
|
|
35
24
|
encryptedColumn: () => encryptedColumn,
|
|
36
25
|
encryptedField: () => encryptedField,
|
|
37
26
|
encryptedTable: () => encryptedTable,
|
|
@@ -920,11 +909,11 @@ var import_result3 = require("@byteslice/result");
|
|
|
920
909
|
|
|
921
910
|
// src/encryption/helpers/model-helpers.ts
|
|
922
911
|
var import_protect_ffi4 = require("@cipherstash/protect-ffi");
|
|
923
|
-
function setNestedValue(obj,
|
|
912
|
+
function setNestedValue(obj, path, value) {
|
|
924
913
|
const FORBIDDEN_KEYS = ["__proto__", "prototype", "constructor"];
|
|
925
914
|
let current = obj;
|
|
926
|
-
for (let i = 0; i <
|
|
927
|
-
const part =
|
|
915
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
916
|
+
const part = path[i];
|
|
928
917
|
if (FORBIDDEN_KEYS.includes(part)) {
|
|
929
918
|
throw new Error(`[encryption]: Forbidden key "${part}" in field path`);
|
|
930
919
|
}
|
|
@@ -933,7 +922,7 @@ function setNestedValue(obj, path2, value) {
|
|
|
933
922
|
}
|
|
934
923
|
current = current[part];
|
|
935
924
|
}
|
|
936
|
-
const lastKey =
|
|
925
|
+
const lastKey = path[path.length - 1];
|
|
937
926
|
if (FORBIDDEN_KEYS.includes(lastKey)) {
|
|
938
927
|
throw new Error(`[encryption]: Forbidden key "${lastKey}" in field path`);
|
|
939
928
|
}
|
|
@@ -1010,7 +999,7 @@ function prepareFieldsForEncryption(model, table) {
|
|
|
1010
999
|
continue;
|
|
1011
1000
|
}
|
|
1012
1001
|
if (typeof value === "object" && !isEncryptedPayload(value) && !columnPaths2.includes(fullKey)) {
|
|
1013
|
-
if (columnPaths2.some((
|
|
1002
|
+
if (columnPaths2.some((path) => path.startsWith(fullKey))) {
|
|
1014
1003
|
processNestedFields(
|
|
1015
1004
|
value,
|
|
1016
1005
|
fullKey,
|
|
@@ -1188,7 +1177,7 @@ function prepareBulkModelsForOperation(models, table) {
|
|
|
1188
1177
|
continue;
|
|
1189
1178
|
}
|
|
1190
1179
|
if (typeof value === "object" && !isEncryptedPayload(value) && !columnPaths.includes(fullKey)) {
|
|
1191
|
-
if (columnPaths.some((
|
|
1180
|
+
if (columnPaths.some((path) => path.startsWith(fullKey))) {
|
|
1192
1181
|
processNestedFields(
|
|
1193
1182
|
value,
|
|
1194
1183
|
fullKey,
|
|
@@ -2798,357 +2787,9 @@ var Encryption = async (config) => {
|
|
|
2798
2787
|
}
|
|
2799
2788
|
return result.data;
|
|
2800
2789
|
};
|
|
2801
|
-
|
|
2802
|
-
// src/utils/config/index.ts
|
|
2803
|
-
var import_node_fs = __toESM(require("fs"), 1);
|
|
2804
|
-
var import_node_path = __toESM(require("path"), 1);
|
|
2805
|
-
function extractWorkspaceIdFromCrn(crn) {
|
|
2806
|
-
const match = crn.match(/crn:[^:]+:([^:]+)$/);
|
|
2807
|
-
if (!match) {
|
|
2808
|
-
throw new Error("Invalid CRN format");
|
|
2809
|
-
}
|
|
2810
|
-
return match[1];
|
|
2811
|
-
}
|
|
2812
|
-
|
|
2813
|
-
// src/secrets/index.ts
|
|
2814
|
-
var Secrets = class {
|
|
2815
|
-
encryptionClient = null;
|
|
2816
|
-
config;
|
|
2817
|
-
apiBaseUrl = process.env.STASH_API_URL || "https://dashboard.cipherstash.com/api/secrets";
|
|
2818
|
-
secretsSchema = encryptedTable("secrets", {
|
|
2819
|
-
value: encryptedColumn("value")
|
|
2820
|
-
});
|
|
2821
|
-
constructor(config) {
|
|
2822
|
-
const workspaceCRN = config.workspaceCRN ?? process.env.CS_WORKSPACE_CRN;
|
|
2823
|
-
const clientId = config.clientId ?? process.env.CS_CLIENT_ID;
|
|
2824
|
-
const clientKey = config.clientKey ?? process.env.CS_CLIENT_KEY;
|
|
2825
|
-
const accessKey = config.accessKey ?? process.env.CS_CLIENT_ACCESS_KEY;
|
|
2826
|
-
if (!workspaceCRN || !clientId || !clientKey || !accessKey) {
|
|
2827
|
-
throw new Error(
|
|
2828
|
-
"Missing required configuration or environment variables."
|
|
2829
|
-
);
|
|
2830
|
-
}
|
|
2831
|
-
this.config = {
|
|
2832
|
-
environment: config.environment,
|
|
2833
|
-
workspaceCRN,
|
|
2834
|
-
clientId,
|
|
2835
|
-
clientKey,
|
|
2836
|
-
accessKey
|
|
2837
|
-
};
|
|
2838
|
-
}
|
|
2839
|
-
initPromise = null;
|
|
2840
|
-
/**
|
|
2841
|
-
* Initialize the Secrets client and underlying Encryption client
|
|
2842
|
-
*/
|
|
2843
|
-
async ensureInitialized() {
|
|
2844
|
-
if (!this.initPromise) {
|
|
2845
|
-
this.initPromise = this._doInit();
|
|
2846
|
-
}
|
|
2847
|
-
return this.initPromise;
|
|
2848
|
-
}
|
|
2849
|
-
async _doInit() {
|
|
2850
|
-
logger.debug("Initializing the Secrets client.");
|
|
2851
|
-
this.encryptionClient = await Encryption({
|
|
2852
|
-
schemas: [this.secretsSchema],
|
|
2853
|
-
config: {
|
|
2854
|
-
workspaceCrn: this.config.workspaceCRN,
|
|
2855
|
-
clientId: this.config.clientId,
|
|
2856
|
-
clientKey: this.config.clientKey,
|
|
2857
|
-
accessKey: this.config.accessKey,
|
|
2858
|
-
keyset: { name: this.config.environment }
|
|
2859
|
-
}
|
|
2860
|
-
});
|
|
2861
|
-
logger.debug("Successfully initialized the Secrets client.");
|
|
2862
|
-
}
|
|
2863
|
-
/**
|
|
2864
|
-
* Get the authorization header for API requests
|
|
2865
|
-
*/
|
|
2866
|
-
getAuthHeader() {
|
|
2867
|
-
return `Bearer ${this.config.accessKey}`;
|
|
2868
|
-
}
|
|
2869
|
-
/**
|
|
2870
|
-
* Make an API request with error handling.
|
|
2871
|
-
*
|
|
2872
|
-
* For GET requests, `params` are appended as URL query parameters.
|
|
2873
|
-
* For POST requests, `body` is sent as JSON in the request body.
|
|
2874
|
-
*/
|
|
2875
|
-
async apiRequest(method, path2, options) {
|
|
2876
|
-
try {
|
|
2877
|
-
let url = `${this.apiBaseUrl}${path2}`;
|
|
2878
|
-
if (options?.params) {
|
|
2879
|
-
const searchParams = new URLSearchParams(options.params);
|
|
2880
|
-
url = `${url}?${searchParams.toString()}`;
|
|
2881
|
-
}
|
|
2882
|
-
logger.debug(`Secrets API request: ${method} ${path2}`);
|
|
2883
|
-
const headers = {
|
|
2884
|
-
"Content-Type": "application/json",
|
|
2885
|
-
Authorization: this.getAuthHeader()
|
|
2886
|
-
};
|
|
2887
|
-
const response = await fetch(url, {
|
|
2888
|
-
method,
|
|
2889
|
-
headers,
|
|
2890
|
-
body: options?.body ? JSON.stringify(options.body) : void 0
|
|
2891
|
-
});
|
|
2892
|
-
if (!response.ok) {
|
|
2893
|
-
const errorText = await response.text();
|
|
2894
|
-
let errorMessage = `API request failed with status ${response.status}`;
|
|
2895
|
-
try {
|
|
2896
|
-
const errorJson = JSON.parse(errorText);
|
|
2897
|
-
errorMessage = errorJson.message || errorJson.error || errorMessage;
|
|
2898
|
-
} catch {
|
|
2899
|
-
errorMessage = errorText || errorMessage;
|
|
2900
|
-
}
|
|
2901
|
-
logger.error(`Secrets API error on ${method} ${path2}: ${errorMessage}`);
|
|
2902
|
-
return {
|
|
2903
|
-
failure: {
|
|
2904
|
-
type: "ApiError",
|
|
2905
|
-
message: errorMessage
|
|
2906
|
-
}
|
|
2907
|
-
};
|
|
2908
|
-
}
|
|
2909
|
-
logger.debug(`Secrets API request successful: ${method} ${path2}`);
|
|
2910
|
-
const data = await response.json();
|
|
2911
|
-
return { data };
|
|
2912
|
-
} catch (error) {
|
|
2913
|
-
const message = error instanceof Error ? error.message : "Unknown network error occurred";
|
|
2914
|
-
logger.error(`Secrets network error on ${method} ${path2}: ${message}`);
|
|
2915
|
-
return {
|
|
2916
|
-
failure: {
|
|
2917
|
-
type: "NetworkError",
|
|
2918
|
-
message
|
|
2919
|
-
}
|
|
2920
|
-
};
|
|
2921
|
-
}
|
|
2922
|
-
}
|
|
2923
|
-
/**
|
|
2924
|
-
* Store an encrypted secret in the vault.
|
|
2925
|
-
* The value is encrypted locally before being sent to the API.
|
|
2926
|
-
*
|
|
2927
|
-
* API: POST /api/secrets/set
|
|
2928
|
-
*
|
|
2929
|
-
* @param name - The name of the secret
|
|
2930
|
-
* @param value - The plaintext value to encrypt and store
|
|
2931
|
-
* @returns A Result containing the API response or an error
|
|
2932
|
-
*/
|
|
2933
|
-
async set(name, value) {
|
|
2934
|
-
logger.debug("Setting secret");
|
|
2935
|
-
await this.ensureInitialized();
|
|
2936
|
-
if (!this.encryptionClient) {
|
|
2937
|
-
return {
|
|
2938
|
-
failure: {
|
|
2939
|
-
type: "ClientError",
|
|
2940
|
-
message: "Failed to initialize Encryption client"
|
|
2941
|
-
}
|
|
2942
|
-
};
|
|
2943
|
-
}
|
|
2944
|
-
const encryptResult = await this.encryptionClient.encrypt(value, {
|
|
2945
|
-
column: this.secretsSchema.value,
|
|
2946
|
-
table: this.secretsSchema
|
|
2947
|
-
});
|
|
2948
|
-
if (encryptResult.failure) {
|
|
2949
|
-
logger.error("Failed to encrypt secret");
|
|
2950
|
-
return {
|
|
2951
|
-
failure: {
|
|
2952
|
-
type: "EncryptionError",
|
|
2953
|
-
message: encryptResult.failure.message
|
|
2954
|
-
}
|
|
2955
|
-
};
|
|
2956
|
-
}
|
|
2957
|
-
const workspaceId = extractWorkspaceIdFromCrn(this.config.workspaceCRN);
|
|
2958
|
-
return await this.apiRequest("POST", "/set", {
|
|
2959
|
-
body: {
|
|
2960
|
-
workspaceId,
|
|
2961
|
-
environment: this.config.environment,
|
|
2962
|
-
name,
|
|
2963
|
-
encryptedValue: encryptedToPgComposite(encryptResult.data)
|
|
2964
|
-
}
|
|
2965
|
-
});
|
|
2966
|
-
}
|
|
2967
|
-
/**
|
|
2968
|
-
* Retrieve and decrypt a secret from the vault.
|
|
2969
|
-
* The secret is decrypted locally after retrieval.
|
|
2970
|
-
*
|
|
2971
|
-
* API: GET /api/secrets/get?workspaceId=...&environment=...&name=...
|
|
2972
|
-
*
|
|
2973
|
-
* @param name - The name of the secret to retrieve
|
|
2974
|
-
* @returns A Result containing the decrypted value or an error
|
|
2975
|
-
*/
|
|
2976
|
-
async get(name) {
|
|
2977
|
-
logger.debug("Getting secret");
|
|
2978
|
-
await this.ensureInitialized();
|
|
2979
|
-
if (!this.encryptionClient) {
|
|
2980
|
-
return {
|
|
2981
|
-
failure: {
|
|
2982
|
-
type: "ClientError",
|
|
2983
|
-
message: "Failed to initialize Encryption client"
|
|
2984
|
-
}
|
|
2985
|
-
};
|
|
2986
|
-
}
|
|
2987
|
-
const workspaceId = extractWorkspaceIdFromCrn(this.config.workspaceCRN);
|
|
2988
|
-
const apiResult = await this.apiRequest("GET", "/get", {
|
|
2989
|
-
params: {
|
|
2990
|
-
workspaceId,
|
|
2991
|
-
environment: this.config.environment,
|
|
2992
|
-
name
|
|
2993
|
-
}
|
|
2994
|
-
});
|
|
2995
|
-
if (apiResult.failure) {
|
|
2996
|
-
return apiResult;
|
|
2997
|
-
}
|
|
2998
|
-
const decryptResult = await this.encryptionClient.decrypt(
|
|
2999
|
-
apiResult.data.encryptedValue.data
|
|
3000
|
-
);
|
|
3001
|
-
if (decryptResult.failure) {
|
|
3002
|
-
logger.error("Failed to decrypt secret");
|
|
3003
|
-
return {
|
|
3004
|
-
failure: {
|
|
3005
|
-
type: "DecryptionError",
|
|
3006
|
-
message: decryptResult.failure.message
|
|
3007
|
-
}
|
|
3008
|
-
};
|
|
3009
|
-
}
|
|
3010
|
-
if (typeof decryptResult.data !== "string") {
|
|
3011
|
-
logger.error("Decrypted secret value is not a string");
|
|
3012
|
-
return {
|
|
3013
|
-
failure: {
|
|
3014
|
-
type: "DecryptionError",
|
|
3015
|
-
message: "Decrypted value is not a string"
|
|
3016
|
-
}
|
|
3017
|
-
};
|
|
3018
|
-
}
|
|
3019
|
-
return { data: decryptResult.data };
|
|
3020
|
-
}
|
|
3021
|
-
/**
|
|
3022
|
-
* Retrieve and decrypt many secrets from the vault.
|
|
3023
|
-
* The secrets are decrypted locally after retrieval.
|
|
3024
|
-
* This method only triggers a single network request to the ZeroKMS.
|
|
3025
|
-
*
|
|
3026
|
-
* API: GET /api/secrets/get-many?workspaceId=...&environment=...&names=name1,name2,...
|
|
3027
|
-
*
|
|
3028
|
-
* Constraints:
|
|
3029
|
-
* - Minimum 2 secret names required
|
|
3030
|
-
* - Maximum 100 secret names per request
|
|
3031
|
-
*
|
|
3032
|
-
* @param names - The names of the secrets to retrieve (min 2, max 100)
|
|
3033
|
-
* @returns A Result containing an object mapping secret names to their decrypted values
|
|
3034
|
-
*/
|
|
3035
|
-
async getMany(names) {
|
|
3036
|
-
logger.debug(`Getting ${names.length} secrets.`);
|
|
3037
|
-
await this.ensureInitialized();
|
|
3038
|
-
if (!this.encryptionClient) {
|
|
3039
|
-
return {
|
|
3040
|
-
failure: {
|
|
3041
|
-
type: "ClientError",
|
|
3042
|
-
message: "Failed to initialize Encryption client"
|
|
3043
|
-
}
|
|
3044
|
-
};
|
|
3045
|
-
}
|
|
3046
|
-
if (names.length < 2) {
|
|
3047
|
-
return {
|
|
3048
|
-
failure: {
|
|
3049
|
-
type: "ClientError",
|
|
3050
|
-
message: "At least 2 secret names are required for getMany"
|
|
3051
|
-
}
|
|
3052
|
-
};
|
|
3053
|
-
}
|
|
3054
|
-
if (names.length > 100) {
|
|
3055
|
-
return {
|
|
3056
|
-
failure: {
|
|
3057
|
-
type: "ClientError",
|
|
3058
|
-
message: "Maximum 100 secret names per request"
|
|
3059
|
-
}
|
|
3060
|
-
};
|
|
3061
|
-
}
|
|
3062
|
-
const workspaceId = extractWorkspaceIdFromCrn(this.config.workspaceCRN);
|
|
3063
|
-
const apiResult = await this.apiRequest(
|
|
3064
|
-
"GET",
|
|
3065
|
-
"/get-many",
|
|
3066
|
-
{
|
|
3067
|
-
params: {
|
|
3068
|
-
workspaceId,
|
|
3069
|
-
environment: this.config.environment,
|
|
3070
|
-
names: names.join(",")
|
|
3071
|
-
}
|
|
3072
|
-
}
|
|
3073
|
-
);
|
|
3074
|
-
if (apiResult.failure) {
|
|
3075
|
-
return apiResult;
|
|
3076
|
-
}
|
|
3077
|
-
const dataToDecrypt = apiResult.data.map((item) => ({
|
|
3078
|
-
name: item.name,
|
|
3079
|
-
value: item.encryptedValue.data
|
|
3080
|
-
}));
|
|
3081
|
-
const decryptResult = await this.encryptionClient.bulkDecryptModels(dataToDecrypt);
|
|
3082
|
-
if (decryptResult.failure) {
|
|
3083
|
-
logger.error(
|
|
3084
|
-
`Failed to decrypt secrets: ${decryptResult.failure.message}`
|
|
3085
|
-
);
|
|
3086
|
-
return {
|
|
3087
|
-
failure: {
|
|
3088
|
-
type: "DecryptionError",
|
|
3089
|
-
message: decryptResult.failure.message
|
|
3090
|
-
}
|
|
3091
|
-
};
|
|
3092
|
-
}
|
|
3093
|
-
const decryptedSecrets = decryptResult.data;
|
|
3094
|
-
const secretsMap = {};
|
|
3095
|
-
for (const secret of decryptedSecrets) {
|
|
3096
|
-
if (secret.name && secret.value) {
|
|
3097
|
-
secretsMap[secret.name] = secret.value;
|
|
3098
|
-
}
|
|
3099
|
-
}
|
|
3100
|
-
return { data: secretsMap };
|
|
3101
|
-
}
|
|
3102
|
-
/**
|
|
3103
|
-
* List all secrets in the environment.
|
|
3104
|
-
* Only names and metadata are returned; values remain encrypted.
|
|
3105
|
-
*
|
|
3106
|
-
* API: GET /api/secrets/list?workspaceId=...&environment=...
|
|
3107
|
-
*
|
|
3108
|
-
* @returns A Result containing the list of secrets or an error
|
|
3109
|
-
*/
|
|
3110
|
-
async list() {
|
|
3111
|
-
logger.debug("Listing secrets.");
|
|
3112
|
-
const workspaceId = extractWorkspaceIdFromCrn(this.config.workspaceCRN);
|
|
3113
|
-
const apiResult = await this.apiRequest(
|
|
3114
|
-
"GET",
|
|
3115
|
-
"/list",
|
|
3116
|
-
{
|
|
3117
|
-
params: {
|
|
3118
|
-
workspaceId,
|
|
3119
|
-
environment: this.config.environment
|
|
3120
|
-
}
|
|
3121
|
-
}
|
|
3122
|
-
);
|
|
3123
|
-
if (apiResult.failure) {
|
|
3124
|
-
return apiResult;
|
|
3125
|
-
}
|
|
3126
|
-
return { data: apiResult.data.secrets };
|
|
3127
|
-
}
|
|
3128
|
-
/**
|
|
3129
|
-
* Delete a secret from the vault.
|
|
3130
|
-
*
|
|
3131
|
-
* API: POST /api/secrets/delete
|
|
3132
|
-
*
|
|
3133
|
-
* @param name - The name of the secret to delete
|
|
3134
|
-
* @returns A Result containing the API response or an error
|
|
3135
|
-
*/
|
|
3136
|
-
async delete(name) {
|
|
3137
|
-
logger.debug("Deleting secret");
|
|
3138
|
-
const workspaceId = extractWorkspaceIdFromCrn(this.config.workspaceCRN);
|
|
3139
|
-
return await this.apiRequest("POST", "/delete", {
|
|
3140
|
-
body: {
|
|
3141
|
-
workspaceId,
|
|
3142
|
-
environment: this.config.environment,
|
|
3143
|
-
name
|
|
3144
|
-
}
|
|
3145
|
-
});
|
|
3146
|
-
}
|
|
3147
|
-
};
|
|
3148
2790
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3149
2791
|
0 && (module.exports = {
|
|
3150
2792
|
Encryption,
|
|
3151
|
-
Secrets,
|
|
3152
2793
|
encryptedColumn,
|
|
3153
2794
|
encryptedField,
|
|
3154
2795
|
encryptedTable,
|