@cipherstash/stack 0.4.0 → 0.5.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 +13 -0
- package/README.md +9 -36
- package/dist/bin/stash.js +215 -179
- package/dist/bin/stash.js.map +1 -1
- package/dist/{chunk-OAPLZLR5.js → chunk-GXGEW6T4.js} +2 -76
- package/dist/chunk-GXGEW6T4.js.map +1 -0
- package/dist/{chunk-KOU2MHXJ.js → chunk-HCL75LXQ.js} +1 -1
- package/dist/chunk-HCL75LXQ.js.map +1 -0
- package/dist/chunk-LBMC4D6D.js +19 -0
- package/dist/chunk-LBMC4D6D.js.map +1 -0
- package/dist/chunk-Q5FTQLYG.js +68 -0
- package/dist/chunk-Q5FTQLYG.js.map +1 -0
- package/dist/{chunk-WUZGKTMG.js → chunk-TGUELLJS.js} +16 -19
- package/dist/chunk-TGUELLJS.js.map +1 -0
- package/dist/chunk-YLISU7FD.js +357 -0
- package/dist/chunk-YLISU7FD.js.map +1 -0
- package/dist/{client-CUGXheRq.d.cts → client-BFrHTgC6.d.ts} +36 -3
- package/dist/{client-CrGQnSlk.d.ts → client-BVQvgvdY.d.cts} +36 -3
- package/dist/client.d.cts +4 -4
- package/dist/client.d.ts +4 -4
- package/dist/drizzle/index.cjs.map +1 -1
- package/dist/drizzle/index.d.cts +14 -6
- package/dist/drizzle/index.d.ts +14 -6
- package/dist/drizzle/index.js +3 -3
- package/dist/drizzle/index.js.map +1 -1
- package/dist/dynamodb/index.cjs +68 -0
- package/dist/dynamodb/index.cjs.map +1 -1
- package/dist/dynamodb/index.d.cts +4 -4
- package/dist/dynamodb/index.d.ts +4 -4
- package/dist/dynamodb/index.js +9 -0
- package/dist/dynamodb/index.js.map +1 -1
- package/dist/encryption/index.cjs +2572 -0
- package/dist/encryption/index.cjs.map +1 -0
- package/dist/encryption/index.d.cts +7 -0
- package/dist/encryption/index.d.ts +7 -0
- package/dist/encryption/index.js +17 -0
- package/dist/encryption/index.js.map +1 -0
- package/dist/errors/index.cjs +44 -0
- package/dist/errors/index.cjs.map +1 -0
- package/dist/errors/index.d.cts +94 -0
- package/dist/errors/index.d.ts +94 -0
- package/dist/errors/index.js +9 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/identity/index.cjs +21 -13
- package/dist/identity/index.cjs.map +1 -1
- package/dist/identity/index.d.cts +78 -2
- package/dist/identity/index.d.ts +78 -2
- package/dist/identity/index.js +7 -3
- package/dist/identity/index.js.map +1 -1
- package/dist/index.cjs +379 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -19
- package/dist/index.d.ts +21 -19
- package/dist/index.js +14 -8
- package/dist/schema/index.d.cts +1 -2
- package/dist/schema/index.d.ts +1 -2
- package/dist/secrets/index.cjs +70 -39
- package/dist/secrets/index.cjs.map +1 -1
- package/dist/secrets/index.d.cts +4 -6
- package/dist/secrets/index.d.ts +4 -6
- package/dist/secrets/index.js +9 -318
- package/dist/secrets/index.js.map +1 -1
- package/dist/supabase/index.cjs +84 -1
- package/dist/supabase/index.cjs.map +1 -1
- package/dist/supabase/index.d.cts +4 -4
- package/dist/supabase/index.d.ts +4 -4
- package/dist/supabase/index.js +24 -1
- package/dist/supabase/index.js.map +1 -1
- package/dist/{types-public-Wj6tB_ux.d.ts → types-public-dKxDzTIL.d.cts} +1 -9
- package/dist/{types-public-Wj6tB_ux.d.cts → types-public-dKxDzTIL.d.ts} +1 -9
- package/dist/types-public.cjs.map +1 -1
- package/dist/types-public.d.cts +1 -2
- package/dist/types-public.d.ts +1 -2
- package/dist/types-public.js +1 -1
- package/package.json +29 -1
- package/dist/chunk-KOU2MHXJ.js.map +0 -1
- package/dist/chunk-OAPLZLR5.js.map +0 -1
- package/dist/chunk-WUZGKTMG.js.map +0 -1
- package/dist/index-9-Ya3fDK.d.cts +0 -169
- package/dist/index-9-Ya3fDK.d.ts +0 -169
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { ProtectErrorCode } from '@cipherstash/protect-ffi';
|
|
2
|
+
|
|
3
|
+
declare const EncryptionErrorTypes: {
|
|
4
|
+
ClientInitError: string;
|
|
5
|
+
EncryptionError: string;
|
|
6
|
+
DecryptionError: string;
|
|
7
|
+
LockContextError: string;
|
|
8
|
+
CtsTokenError: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Base error interface returned by all encryption operations.
|
|
12
|
+
*
|
|
13
|
+
* Every operation that can fail returns `Result<T, EncryptionError>`.
|
|
14
|
+
* Use the `type` field to narrow to a specific error kind, or use
|
|
15
|
+
* {@link StackError} for an exhaustive discriminated union.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* const result = await client.encrypt(value, opts)
|
|
20
|
+
* if (result.failure) {
|
|
21
|
+
* switch (result.failure.type) {
|
|
22
|
+
* case EncryptionErrorTypes.EncryptionError:
|
|
23
|
+
* console.error('Encryption failed:', result.failure.message)
|
|
24
|
+
* break
|
|
25
|
+
* case EncryptionErrorTypes.LockContextError:
|
|
26
|
+
* console.error('Lock context issue:', result.failure.message)
|
|
27
|
+
* break
|
|
28
|
+
* }
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
interface EncryptionError {
|
|
33
|
+
type: (typeof EncryptionErrorTypes)[keyof typeof EncryptionErrorTypes];
|
|
34
|
+
message: string;
|
|
35
|
+
code?: ProtectErrorCode;
|
|
36
|
+
}
|
|
37
|
+
interface ClientInitError {
|
|
38
|
+
type: typeof EncryptionErrorTypes.ClientInitError;
|
|
39
|
+
message: string;
|
|
40
|
+
}
|
|
41
|
+
interface EncryptionOperationError {
|
|
42
|
+
type: typeof EncryptionErrorTypes.EncryptionError;
|
|
43
|
+
message: string;
|
|
44
|
+
code?: ProtectErrorCode;
|
|
45
|
+
}
|
|
46
|
+
interface DecryptionOperationError {
|
|
47
|
+
type: typeof EncryptionErrorTypes.DecryptionError;
|
|
48
|
+
message: string;
|
|
49
|
+
code?: ProtectErrorCode;
|
|
50
|
+
}
|
|
51
|
+
interface LockContextError {
|
|
52
|
+
type: typeof EncryptionErrorTypes.LockContextError;
|
|
53
|
+
message: string;
|
|
54
|
+
}
|
|
55
|
+
interface CtsTokenError {
|
|
56
|
+
type: typeof EncryptionErrorTypes.CtsTokenError;
|
|
57
|
+
message: string;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Discriminated union of all specific error types.
|
|
61
|
+
*
|
|
62
|
+
* Use `StackError` when you need exhaustive error handling via `switch` on the `type` field.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```typescript
|
|
66
|
+
* function handleError(error: StackError) {
|
|
67
|
+
* switch (error.type) {
|
|
68
|
+
* case 'ClientInitError':
|
|
69
|
+
* // re-initialize client
|
|
70
|
+
* break
|
|
71
|
+
* case 'EncryptionError':
|
|
72
|
+
* case 'DecryptionError':
|
|
73
|
+
* // log and retry
|
|
74
|
+
* break
|
|
75
|
+
* case 'LockContextError':
|
|
76
|
+
* // re-authenticate
|
|
77
|
+
* break
|
|
78
|
+
* case 'CtsTokenError':
|
|
79
|
+
* // refresh token
|
|
80
|
+
* break
|
|
81
|
+
* default:
|
|
82
|
+
* error satisfies never
|
|
83
|
+
* }
|
|
84
|
+
* }
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
type StackError = ClientInitError | EncryptionOperationError | DecryptionOperationError | LockContextError | CtsTokenError;
|
|
88
|
+
/**
|
|
89
|
+
* Safely extract an error message from an unknown thrown value.
|
|
90
|
+
* Unlike `(error as Error).message`, this handles non-Error values gracefully.
|
|
91
|
+
*/
|
|
92
|
+
declare function getErrorMessage(error: unknown): string;
|
|
93
|
+
|
|
94
|
+
export { type ClientInitError, type CtsTokenError, type DecryptionOperationError, type EncryptionError, EncryptionErrorTypes, type EncryptionOperationError, type LockContextError, type StackError, getErrorMessage };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { ProtectErrorCode } from '@cipherstash/protect-ffi';
|
|
2
|
+
|
|
3
|
+
declare const EncryptionErrorTypes: {
|
|
4
|
+
ClientInitError: string;
|
|
5
|
+
EncryptionError: string;
|
|
6
|
+
DecryptionError: string;
|
|
7
|
+
LockContextError: string;
|
|
8
|
+
CtsTokenError: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Base error interface returned by all encryption operations.
|
|
12
|
+
*
|
|
13
|
+
* Every operation that can fail returns `Result<T, EncryptionError>`.
|
|
14
|
+
* Use the `type` field to narrow to a specific error kind, or use
|
|
15
|
+
* {@link StackError} for an exhaustive discriminated union.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* const result = await client.encrypt(value, opts)
|
|
20
|
+
* if (result.failure) {
|
|
21
|
+
* switch (result.failure.type) {
|
|
22
|
+
* case EncryptionErrorTypes.EncryptionError:
|
|
23
|
+
* console.error('Encryption failed:', result.failure.message)
|
|
24
|
+
* break
|
|
25
|
+
* case EncryptionErrorTypes.LockContextError:
|
|
26
|
+
* console.error('Lock context issue:', result.failure.message)
|
|
27
|
+
* break
|
|
28
|
+
* }
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
interface EncryptionError {
|
|
33
|
+
type: (typeof EncryptionErrorTypes)[keyof typeof EncryptionErrorTypes];
|
|
34
|
+
message: string;
|
|
35
|
+
code?: ProtectErrorCode;
|
|
36
|
+
}
|
|
37
|
+
interface ClientInitError {
|
|
38
|
+
type: typeof EncryptionErrorTypes.ClientInitError;
|
|
39
|
+
message: string;
|
|
40
|
+
}
|
|
41
|
+
interface EncryptionOperationError {
|
|
42
|
+
type: typeof EncryptionErrorTypes.EncryptionError;
|
|
43
|
+
message: string;
|
|
44
|
+
code?: ProtectErrorCode;
|
|
45
|
+
}
|
|
46
|
+
interface DecryptionOperationError {
|
|
47
|
+
type: typeof EncryptionErrorTypes.DecryptionError;
|
|
48
|
+
message: string;
|
|
49
|
+
code?: ProtectErrorCode;
|
|
50
|
+
}
|
|
51
|
+
interface LockContextError {
|
|
52
|
+
type: typeof EncryptionErrorTypes.LockContextError;
|
|
53
|
+
message: string;
|
|
54
|
+
}
|
|
55
|
+
interface CtsTokenError {
|
|
56
|
+
type: typeof EncryptionErrorTypes.CtsTokenError;
|
|
57
|
+
message: string;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Discriminated union of all specific error types.
|
|
61
|
+
*
|
|
62
|
+
* Use `StackError` when you need exhaustive error handling via `switch` on the `type` field.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```typescript
|
|
66
|
+
* function handleError(error: StackError) {
|
|
67
|
+
* switch (error.type) {
|
|
68
|
+
* case 'ClientInitError':
|
|
69
|
+
* // re-initialize client
|
|
70
|
+
* break
|
|
71
|
+
* case 'EncryptionError':
|
|
72
|
+
* case 'DecryptionError':
|
|
73
|
+
* // log and retry
|
|
74
|
+
* break
|
|
75
|
+
* case 'LockContextError':
|
|
76
|
+
* // re-authenticate
|
|
77
|
+
* break
|
|
78
|
+
* case 'CtsTokenError':
|
|
79
|
+
* // refresh token
|
|
80
|
+
* break
|
|
81
|
+
* default:
|
|
82
|
+
* error satisfies never
|
|
83
|
+
* }
|
|
84
|
+
* }
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
type StackError = ClientInitError | EncryptionOperationError | DecryptionOperationError | LockContextError | CtsTokenError;
|
|
88
|
+
/**
|
|
89
|
+
* Safely extract an error message from an unknown thrown value.
|
|
90
|
+
* Unlike `(error as Error).message`, this handles non-Error values gracefully.
|
|
91
|
+
*/
|
|
92
|
+
declare function getErrorMessage(error: unknown): string;
|
|
93
|
+
|
|
94
|
+
export { type ClientInitError, type CtsTokenError, type DecryptionOperationError, type EncryptionError, EncryptionErrorTypes, type EncryptionOperationError, type LockContextError, type StackError, getErrorMessage };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/identity/index.cjs
CHANGED
|
@@ -108,25 +108,33 @@ function loadWorkSpaceId(suppliedCrn) {
|
|
|
108
108
|
|
|
109
109
|
// src/utils/logger/index.ts
|
|
110
110
|
var import_evlog = require("evlog");
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
111
|
+
var validLevels = ["debug", "info", "error"];
|
|
112
|
+
function levelFromEnv() {
|
|
113
|
+
const env = process.env.STASH_STACK_LOG;
|
|
114
|
+
if (env && validLevels.includes(env)) return env;
|
|
115
|
+
return "error";
|
|
116
|
+
}
|
|
117
|
+
function samplingRatesForLevel(level) {
|
|
118
|
+
switch (level) {
|
|
119
|
+
case "debug":
|
|
120
|
+
return { debug: 100, info: 100, warn: 100, error: 100 };
|
|
121
|
+
case "info":
|
|
122
|
+
return { debug: 0, info: 100, warn: 100, error: 100 };
|
|
123
|
+
case "error":
|
|
124
|
+
default:
|
|
125
|
+
return { debug: 0, info: 0, warn: 0, error: 100 };
|
|
126
|
+
}
|
|
118
127
|
}
|
|
119
128
|
var initialized = false;
|
|
120
|
-
function initStackLogger(
|
|
129
|
+
function initStackLogger() {
|
|
121
130
|
if (initialized) return;
|
|
122
131
|
initialized = true;
|
|
123
|
-
const
|
|
132
|
+
const level = levelFromEnv();
|
|
133
|
+
const rates = samplingRatesForLevel(level);
|
|
124
134
|
(0, import_evlog.initLogger)({
|
|
125
135
|
env: { service: "@cipherstash/stack" },
|
|
126
|
-
enabled:
|
|
127
|
-
|
|
128
|
-
...rates && { sampling: { rates } },
|
|
129
|
-
...config?.drain && { drain: config.drain }
|
|
136
|
+
enabled: true,
|
|
137
|
+
sampling: { rates }
|
|
130
138
|
});
|
|
131
139
|
}
|
|
132
140
|
initStackLogger();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/identity/index.ts","../../src/errors/index.ts","../../src/utils/config/index.ts","../../src/utils/logger/index.ts"],"sourcesContent":["import { type EncryptionError, EncryptionErrorTypes } from '@/errors'\nimport { loadWorkSpaceId } from '@/utils/config'\nimport { logger } from '@/utils/logger'\nimport { type Result, withResult } from '@byteslice/result'\n\nexport type CtsRegions = 'ap-southeast-2'\n\nexport type IdentifyOptions = {\n fetchFromCts?: boolean\n}\n\nexport type CtsToken = {\n accessToken: string\n expiry: number\n}\n\nexport type Context = {\n identityClaim: string[]\n}\n\nexport type LockContextOptions = {\n context?: Context\n ctsToken?: CtsToken\n}\n\nexport type GetLockContextResponse = {\n ctsToken: CtsToken\n context: Context\n}\n\n/**\n * Manages CipherStash lock contexts for row-level access control.\n *\n * A `LockContext` ties encryption/decryption operations to an authenticated\n * user identity via CTS (CipherStash Token Service). Call {@link identify}\n * with a user's JWT to obtain a CTS token, then pass the `LockContext`\n * to `.withLockContext()` on any encrypt/decrypt operation.\n *\n * @example\n * ```typescript\n * import { LockContext } from \"@cipherstash/stack/identity\"\n *\n * const lc = new LockContext()\n * const identified = await lc.identify(userJwt)\n *\n * if (identified.failure) throw new Error(identified.failure.message)\n *\n * const result = await client\n * .encrypt(value, { column: users.email, table: users })\n * .withLockContext(identified.data)\n * ```\n */\nexport class LockContext {\n private ctsToken: CtsToken | undefined\n private workspaceId: string\n private context: Context\n\n constructor({\n context = { identityClaim: ['sub'] },\n ctsToken,\n }: LockContextOptions = {}) {\n const workspaceId = loadWorkSpaceId()\n\n if (!workspaceId) {\n throw new Error(\n 'You have not defined a workspace ID in your config file, or the CS_WORKSPACE_CRN environment variable.',\n )\n }\n\n if (ctsToken) {\n this.ctsToken = ctsToken\n }\n\n this.workspaceId = workspaceId\n this.context = context\n logger.debug('Successfully initialized the EQL lock context.')\n }\n\n /**\n * Exchange a user's JWT for a CTS token and bind it to this lock context.\n *\n * @param jwtToken - A valid OIDC / JWT token for the current user.\n * @returns A `Result` containing this `LockContext` (now authenticated) or an error.\n *\n * @example\n * ```typescript\n * const lc = new LockContext()\n * const result = await lc.identify(userJwt)\n * if (result.failure) {\n * console.error(\"Auth failed:\", result.failure.message)\n * }\n * ```\n */\n async identify(\n jwtToken: string,\n ): Promise<Result<LockContext, EncryptionError>> {\n const workspaceId = this.workspaceId\n\n const ctsEndpoint =\n process.env.CS_CTS_ENDPOINT ||\n 'https://ap-southeast-2.aws.auth.viturhosted.net'\n\n const ctsFetchResult = await withResult(\n () =>\n fetch(`${ctsEndpoint}/api/authorize`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n workspaceId,\n oidcToken: jwtToken,\n }),\n }),\n (error) => ({\n type: EncryptionErrorTypes.CtsTokenError,\n message: error.message,\n }),\n )\n\n if (ctsFetchResult.failure) {\n return ctsFetchResult\n }\n\n const identifiedLockContext = await withResult(\n async () => {\n const ctsToken = (await ctsFetchResult.data.json()) as CtsToken\n\n if (!ctsToken.accessToken) {\n throw new Error(\n 'The response from the CipherStash API did not contain an access token. Please contact support.',\n )\n }\n\n this.ctsToken = ctsToken\n return this\n },\n (error) => ({\n type: EncryptionErrorTypes.CtsTokenError,\n message: error.message,\n }),\n )\n\n return identifiedLockContext\n }\n\n /**\n * Retrieve the current CTS token and context for use with encryption operations.\n *\n * Must be called after {@link identify}. Returns the token/context pair that\n * `.withLockContext()` expects.\n *\n * @returns A `Result` containing the CTS token and identity context, or an error\n * if {@link identify} has not been called.\n */\n getLockContext(): Promise<Result<GetLockContextResponse, EncryptionError>> {\n return withResult(\n () => {\n if (!this.ctsToken?.accessToken || !this.ctsToken?.expiry) {\n throw new Error(\n 'The CTS token is not set. Please call identify() with a users JWT token, or pass an existing CTS token to the LockContext constructor before calling getLockContext().',\n )\n }\n\n return {\n context: this.context,\n ctsToken: this.ctsToken,\n }\n },\n (error) => ({\n type: EncryptionErrorTypes.CtsTokenError,\n message: error.message,\n }),\n )\n }\n}\n","import type { ProtectErrorCode } from '@cipherstash/protect-ffi'\n\nexport const EncryptionErrorTypes = {\n ClientInitError: 'ClientInitError',\n EncryptionError: 'EncryptionError',\n DecryptionError: 'DecryptionError',\n LockContextError: 'LockContextError',\n CtsTokenError: 'CtsTokenError',\n}\n\n/**\n * Base error interface returned by all encryption operations.\n *\n * Every operation that can fail returns `Result<T, EncryptionError>`.\n * Use the `type` field to narrow to a specific error kind, or use\n * {@link StackError} for an exhaustive discriminated union.\n *\n * @example\n * ```typescript\n * const result = await client.encrypt(value, opts)\n * if (result.failure) {\n * switch (result.failure.type) {\n * case 'EncryptionError':\n * console.error('Encryption failed:', result.failure.message)\n * break\n * case 'LockContextError':\n * console.error('Lock context issue:', result.failure.message)\n * break\n * }\n * }\n * ```\n */\nexport interface EncryptionError {\n type: (typeof EncryptionErrorTypes)[keyof typeof EncryptionErrorTypes]\n message: string\n code?: ProtectErrorCode\n}\n\n// ---------------------------------------------------------------------------\n// Specific error types (discriminated union members)\n// ---------------------------------------------------------------------------\n\nexport interface ClientInitError {\n type: typeof EncryptionErrorTypes.ClientInitError\n message: string\n}\n\nexport interface EncryptionOperationError {\n type: typeof EncryptionErrorTypes.EncryptionError\n message: string\n code?: ProtectErrorCode\n}\n\nexport interface DecryptionOperationError {\n type: typeof EncryptionErrorTypes.DecryptionError\n message: string\n code?: ProtectErrorCode\n}\n\nexport interface LockContextError {\n type: typeof EncryptionErrorTypes.LockContextError\n message: string\n}\n\nexport interface CtsTokenError {\n type: typeof EncryptionErrorTypes.CtsTokenError\n message: string\n}\n\n/**\n * Discriminated union of all specific error types.\n *\n * Use `StackError` when you need exhaustive error handling via `switch` on the `type` field.\n *\n * @example\n * ```typescript\n * function handleError(error: StackError) {\n * switch (error.type) {\n * case 'ClientInitError':\n * // re-initialize client\n * break\n * case 'EncryptionError':\n * case 'DecryptionError':\n * // log and retry\n * break\n * case 'LockContextError':\n * // re-authenticate\n * break\n * case 'CtsTokenError':\n * // refresh token\n * break\n * default:\n * error satisfies never\n * }\n * }\n * ```\n */\nexport type StackError =\n | ClientInitError\n | EncryptionOperationError\n | DecryptionOperationError\n | LockContextError\n | CtsTokenError\n\n// ---------------------------------------------------------------------------\n// Error utilities\n// ---------------------------------------------------------------------------\n\n/**\n * Safely extract an error message from an unknown thrown value.\n * Unlike `(error as Error).message`, this handles non-Error values gracefully.\n */\nexport function getErrorMessage(error: unknown): string {\n if (error instanceof Error) return error.message\n if (typeof error === 'string') return error\n return String(error)\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\n\n/**\n * A lightweight function that parses a TOML-like string\n * and returns the `workspace_crn` value found under `[auth]`.\n *\n * @param tomlString The contents of the TOML file as a string.\n * @returns The workspace_crn if found, otherwise undefined.\n */\nfunction getWorkspaceCrn(tomlString: string): string | undefined {\n let currentSection = ''\n let workspaceCrn: string | undefined\n\n const lines = tomlString.split(/\\r?\\n/)\n\n for (const line of lines) {\n const trimmedLine = line.trim()\n\n if (!trimmedLine || trimmedLine.startsWith('#')) {\n continue\n }\n\n const sectionMatch = trimmedLine.match(/^\\[([^\\]]+)\\]$/)\n if (sectionMatch) {\n currentSection = sectionMatch[1]\n continue\n }\n\n const kvMatch = trimmedLine.match(/^(\\w+)\\s*=\\s*\"([^\"]+)\"$/)\n if (kvMatch) {\n const [_, key, value] = kvMatch\n\n if (currentSection === 'auth' && key === 'workspace_crn') {\n workspaceCrn = value\n break\n }\n }\n }\n\n return workspaceCrn\n}\n\n/**\n * Extracts the workspace ID from a CRN string.\n * CRN format: crn:region.aws:ID\n *\n * @param crn The CRN string to extract from\n * @returns The workspace ID portion of the CRN\n */\nexport function extractWorkspaceIdFromCrn(crn: string): string {\n const match = crn.match(/crn:[^:]+:([^:]+)$/)\n if (!match) {\n throw new Error('Invalid CRN format')\n }\n return match[1]\n}\n\nexport function loadWorkSpaceId(suppliedCrn?: string): string {\n const configPath = path.join(process.cwd(), 'cipherstash.toml')\n\n if (suppliedCrn) {\n return extractWorkspaceIdFromCrn(suppliedCrn)\n }\n\n if (!fs.existsSync(configPath) && !process.env.CS_WORKSPACE_CRN) {\n throw new Error(\n 'You have not defined a workspace CRN in your config file, or the CS_WORKSPACE_CRN environment variable.',\n )\n }\n\n // Environment variables take precedence over config files\n if (process.env.CS_WORKSPACE_CRN) {\n return extractWorkspaceIdFromCrn(process.env.CS_WORKSPACE_CRN)\n }\n\n if (!fs.existsSync(configPath)) {\n throw new Error(\n 'You have not defined a workspace CRN in your config file, or the CS_WORKSPACE_CRN environment variable.',\n )\n }\n\n const tomlString = fs.readFileSync(configPath, 'utf8')\n const workspaceCrn = getWorkspaceCrn(tomlString)\n\n if (!workspaceCrn) {\n throw new Error(\n 'You have not defined a workspace CRN in your config file, or the CS_WORKSPACE_CRN environment variable.',\n )\n }\n\n return extractWorkspaceIdFromCrn(workspaceCrn)\n}\n","import { initLogger, createRequestLogger } from 'evlog'\nimport type { LoggerConfig } from 'evlog'\n\nexport type LoggingConfig = {\n enabled?: boolean\n pretty?: boolean\n drain?: LoggerConfig['drain']\n}\n\nfunction samplingFromEnv() {\n const env = process.env.STASH_LOG_LEVEL\n if (!env) return undefined\n const levels = ['debug', 'info', 'warn', 'error'] as const\n const idx = levels.indexOf(env as (typeof levels)[number])\n if (idx === -1) return undefined\n return Object.fromEntries(levels.map((l, i) => [l, i >= idx ? 100 : 0]))\n}\n\nlet initialized = false\n\nexport function initStackLogger(config?: LoggingConfig): void {\n if (initialized) return\n initialized = true\n const rates = samplingFromEnv()\n initLogger({\n env: { service: '@cipherstash/stack' },\n enabled: config?.enabled ?? !!rates,\n pretty: config?.pretty,\n ...(rates && { sampling: { rates } }),\n ...(config?.drain && { drain: config.drain }),\n })\n}\n\n// Auto-init with defaults on first import\ninitStackLogger()\n\nexport { createRequestLogger }\n\n// Stringify only the first arg (the message string); drop subsequent args\n// which may contain sensitive objects (e.g. encryptConfig, plaintext).\nfunction safeMessage(args: unknown[]): string {\n return typeof args[0] === 'string' ? args[0] : ''\n}\n\n// Legacy logger for simple one-off logs (used by encryption/ffi/index.ts + identity/index.ts)\nexport const logger = {\n debug(...args: unknown[]) {\n const log = createRequestLogger()\n log.set({\n level: 'debug',\n source: '@cipherstash/stack',\n message: safeMessage(args),\n })\n log.emit()\n },\n info(...args: unknown[]) {\n const log = createRequestLogger()\n log.set({ source: '@cipherstash/stack' })\n log.info(safeMessage(args))\n log.emit()\n },\n warn(...args: unknown[]) {\n const log = createRequestLogger()\n log.warn(safeMessage(args))\n log.emit()\n },\n error(...args: unknown[]) {\n const log = createRequestLogger()\n log.error(safeMessage(args))\n log.emit()\n },\n}\n"],"mappings":"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","names":["path","fs"]}
|
|
1
|
+
{"version":3,"sources":["../../src/identity/index.ts","../../src/errors/index.ts","../../src/utils/config/index.ts","../../src/utils/logger/index.ts"],"sourcesContent":["import { type EncryptionError, EncryptionErrorTypes } from '@/errors'\nimport { loadWorkSpaceId } from '@/utils/config'\nimport { logger } from '@/utils/logger'\nimport { type Result, withResult } from '@byteslice/result'\n\nexport type CtsRegions = 'ap-southeast-2'\n\nexport type IdentifyOptions = {\n fetchFromCts?: boolean\n}\n\nexport type CtsToken = {\n accessToken: string\n expiry: number\n}\n\nexport type Context = {\n identityClaim: string[]\n}\n\nexport type LockContextOptions = {\n context?: Context\n ctsToken?: CtsToken\n}\n\nexport type GetLockContextResponse = {\n ctsToken: CtsToken\n context: Context\n}\n\n/**\n * Manages CipherStash lock contexts for row-level access control.\n *\n * A `LockContext` ties encryption/decryption operations to an authenticated\n * user identity via CTS (CipherStash Token Service). Call {@link identify}\n * with a user's JWT to obtain a CTS token, then pass the `LockContext`\n * to `.withLockContext()` on any encrypt/decrypt operation.\n *\n * @example\n * ```typescript\n * import { LockContext } from \"@cipherstash/stack/identity\"\n *\n * const lc = new LockContext()\n * const identified = await lc.identify(userJwt)\n *\n * if (identified.failure) throw new Error(identified.failure.message)\n *\n * const result = await client\n * .encrypt(value, { column: users.email, table: users })\n * .withLockContext(identified.data)\n * ```\n */\nexport class LockContext {\n private ctsToken: CtsToken | undefined\n private workspaceId: string\n private context: Context\n\n constructor({\n context = { identityClaim: ['sub'] },\n ctsToken,\n }: LockContextOptions = {}) {\n const workspaceId = loadWorkSpaceId()\n\n if (!workspaceId) {\n throw new Error(\n 'You have not defined a workspace ID in your config file, or the CS_WORKSPACE_CRN environment variable.',\n )\n }\n\n if (ctsToken) {\n this.ctsToken = ctsToken\n }\n\n this.workspaceId = workspaceId\n this.context = context\n logger.debug('Successfully initialized the EQL lock context.')\n }\n\n /**\n * Exchange a user's JWT for a CTS token and bind it to this lock context.\n *\n * @param jwtToken - A valid OIDC / JWT token for the current user.\n * @returns A `Result` containing this `LockContext` (now authenticated) or an error.\n *\n * @example\n * ```typescript\n * const lc = new LockContext()\n * const result = await lc.identify(userJwt)\n * if (result.failure) {\n * console.error(\"Auth failed:\", result.failure.message)\n * }\n * ```\n */\n async identify(\n jwtToken: string,\n ): Promise<Result<LockContext, EncryptionError>> {\n const workspaceId = this.workspaceId\n\n const ctsEndpoint =\n process.env.CS_CTS_ENDPOINT ||\n 'https://ap-southeast-2.aws.auth.viturhosted.net'\n\n const ctsFetchResult = await withResult(\n () =>\n fetch(`${ctsEndpoint}/api/authorize`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n workspaceId,\n oidcToken: jwtToken,\n }),\n }),\n (error) => ({\n type: EncryptionErrorTypes.CtsTokenError,\n message: error.message,\n }),\n )\n\n if (ctsFetchResult.failure) {\n return ctsFetchResult\n }\n\n const identifiedLockContext = await withResult(\n async () => {\n const ctsToken = (await ctsFetchResult.data.json()) as CtsToken\n\n if (!ctsToken.accessToken) {\n throw new Error(\n 'The response from the CipherStash API did not contain an access token. Please contact support.',\n )\n }\n\n this.ctsToken = ctsToken\n return this\n },\n (error) => ({\n type: EncryptionErrorTypes.CtsTokenError,\n message: error.message,\n }),\n )\n\n return identifiedLockContext\n }\n\n /**\n * Retrieve the current CTS token and context for use with encryption operations.\n *\n * Must be called after {@link identify}. Returns the token/context pair that\n * `.withLockContext()` expects.\n *\n * @returns A `Result` containing the CTS token and identity context, or an error\n * if {@link identify} has not been called.\n */\n getLockContext(): Promise<Result<GetLockContextResponse, EncryptionError>> {\n return withResult(\n () => {\n if (!this.ctsToken?.accessToken || !this.ctsToken?.expiry) {\n throw new Error(\n 'The CTS token is not set. Please call identify() with a users JWT token, or pass an existing CTS token to the LockContext constructor before calling getLockContext().',\n )\n }\n\n return {\n context: this.context,\n ctsToken: this.ctsToken,\n }\n },\n (error) => ({\n type: EncryptionErrorTypes.CtsTokenError,\n message: error.message,\n }),\n )\n }\n}\n","import type { ProtectErrorCode } from '@cipherstash/protect-ffi'\n\nexport const EncryptionErrorTypes = {\n ClientInitError: 'ClientInitError',\n EncryptionError: 'EncryptionError',\n DecryptionError: 'DecryptionError',\n LockContextError: 'LockContextError',\n CtsTokenError: 'CtsTokenError',\n}\n\n/**\n * Base error interface returned by all encryption operations.\n *\n * Every operation that can fail returns `Result<T, EncryptionError>`.\n * Use the `type` field to narrow to a specific error kind, or use\n * {@link StackError} for an exhaustive discriminated union.\n *\n * @example\n * ```typescript\n * const result = await client.encrypt(value, opts)\n * if (result.failure) {\n * switch (result.failure.type) {\n * case EncryptionErrorTypes.EncryptionError:\n * console.error('Encryption failed:', result.failure.message)\n * break\n * case EncryptionErrorTypes.LockContextError:\n * console.error('Lock context issue:', result.failure.message)\n * break\n * }\n * }\n * ```\n */\nexport interface EncryptionError {\n type: (typeof EncryptionErrorTypes)[keyof typeof EncryptionErrorTypes]\n message: string\n code?: ProtectErrorCode\n}\n\n// ---------------------------------------------------------------------------\n// Specific error types (discriminated union members)\n// ---------------------------------------------------------------------------\n\nexport interface ClientInitError {\n type: typeof EncryptionErrorTypes.ClientInitError\n message: string\n}\n\nexport interface EncryptionOperationError {\n type: typeof EncryptionErrorTypes.EncryptionError\n message: string\n code?: ProtectErrorCode\n}\n\nexport interface DecryptionOperationError {\n type: typeof EncryptionErrorTypes.DecryptionError\n message: string\n code?: ProtectErrorCode\n}\n\nexport interface LockContextError {\n type: typeof EncryptionErrorTypes.LockContextError\n message: string\n}\n\nexport interface CtsTokenError {\n type: typeof EncryptionErrorTypes.CtsTokenError\n message: string\n}\n\n/**\n * Discriminated union of all specific error types.\n *\n * Use `StackError` when you need exhaustive error handling via `switch` on the `type` field.\n *\n * @example\n * ```typescript\n * function handleError(error: StackError) {\n * switch (error.type) {\n * case 'ClientInitError':\n * // re-initialize client\n * break\n * case 'EncryptionError':\n * case 'DecryptionError':\n * // log and retry\n * break\n * case 'LockContextError':\n * // re-authenticate\n * break\n * case 'CtsTokenError':\n * // refresh token\n * break\n * default:\n * error satisfies never\n * }\n * }\n * ```\n */\nexport type StackError =\n | ClientInitError\n | EncryptionOperationError\n | DecryptionOperationError\n | LockContextError\n | CtsTokenError\n\n// ---------------------------------------------------------------------------\n// Error utilities\n// ---------------------------------------------------------------------------\n\n/**\n * Safely extract an error message from an unknown thrown value.\n * Unlike `(error as Error).message`, this handles non-Error values gracefully.\n */\nexport function getErrorMessage(error: unknown): string {\n if (error instanceof Error) return error.message\n if (typeof error === 'string') return error\n return String(error)\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\n\n/**\n * A lightweight function that parses a TOML-like string\n * and returns the `workspace_crn` value found under `[auth]`.\n *\n * @param tomlString The contents of the TOML file as a string.\n * @returns The workspace_crn if found, otherwise undefined.\n */\nfunction getWorkspaceCrn(tomlString: string): string | undefined {\n let currentSection = ''\n let workspaceCrn: string | undefined\n\n const lines = tomlString.split(/\\r?\\n/)\n\n for (const line of lines) {\n const trimmedLine = line.trim()\n\n if (!trimmedLine || trimmedLine.startsWith('#')) {\n continue\n }\n\n const sectionMatch = trimmedLine.match(/^\\[([^\\]]+)\\]$/)\n if (sectionMatch) {\n currentSection = sectionMatch[1]\n continue\n }\n\n const kvMatch = trimmedLine.match(/^(\\w+)\\s*=\\s*\"([^\"]+)\"$/)\n if (kvMatch) {\n const [_, key, value] = kvMatch\n\n if (currentSection === 'auth' && key === 'workspace_crn') {\n workspaceCrn = value\n break\n }\n }\n }\n\n return workspaceCrn\n}\n\n/**\n * Extracts the workspace ID from a CRN string.\n * CRN format: crn:region.aws:ID\n *\n * @param crn The CRN string to extract from\n * @returns The workspace ID portion of the CRN\n */\nexport function extractWorkspaceIdFromCrn(crn: string): string {\n const match = crn.match(/crn:[^:]+:([^:]+)$/)\n if (!match) {\n throw new Error('Invalid CRN format')\n }\n return match[1]\n}\n\nexport function loadWorkSpaceId(suppliedCrn?: string): string {\n const configPath = path.join(process.cwd(), 'cipherstash.toml')\n\n if (suppliedCrn) {\n return extractWorkspaceIdFromCrn(suppliedCrn)\n }\n\n if (!fs.existsSync(configPath) && !process.env.CS_WORKSPACE_CRN) {\n throw new Error(\n 'You have not defined a workspace CRN in your config file, or the CS_WORKSPACE_CRN environment variable.',\n )\n }\n\n // Environment variables take precedence over config files\n if (process.env.CS_WORKSPACE_CRN) {\n return extractWorkspaceIdFromCrn(process.env.CS_WORKSPACE_CRN)\n }\n\n if (!fs.existsSync(configPath)) {\n throw new Error(\n 'You have not defined a workspace CRN in your config file, or the CS_WORKSPACE_CRN environment variable.',\n )\n }\n\n const tomlString = fs.readFileSync(configPath, 'utf8')\n const workspaceCrn = getWorkspaceCrn(tomlString)\n\n if (!workspaceCrn) {\n throw new Error(\n 'You have not defined a workspace CRN in your config file, or the CS_WORKSPACE_CRN environment variable.',\n )\n }\n\n return extractWorkspaceIdFromCrn(workspaceCrn)\n}\n","import { createRequestLogger, initLogger } from 'evlog'\n\n/**\n * Log level for the Stack logger.\n *\n * Configured via the `STASH_STACK_LOG` environment variable.\n *\n * - `'error'` — Only errors (default when `STASH_STACK_LOG` is not set).\n * - `'info'` — Info and errors.\n * - `'debug'` — Debug, info, and errors.\n */\nexport type LogLevel = 'debug' | 'info' | 'error'\n\nconst validLevels: readonly LogLevel[] = ['debug', 'info', 'error'] as const\n\nfunction levelFromEnv(): LogLevel {\n const env = process.env.STASH_STACK_LOG\n if (env && validLevels.includes(env as LogLevel)) return env as LogLevel\n return 'error'\n}\n\nfunction samplingRatesForLevel(level: LogLevel): Record<string, number> {\n // evlog uses sampling rates: 100 = always emit, 0 = never emit\n switch (level) {\n case 'debug':\n return { debug: 100, info: 100, warn: 100, error: 100 }\n case 'info':\n return { debug: 0, info: 100, warn: 100, error: 100 }\n case 'error':\n default:\n return { debug: 0, info: 0, warn: 0, error: 100 }\n }\n}\n\nlet initialized = false\n\n/**\n * Initialize the Stack logger.\n *\n * The log level is read from the `STASH_STACK_LOG` environment variable.\n * When the variable is not set, the default is `'error'` (errors only).\n *\n * @internal\n */\nexport function initStackLogger(): void {\n if (initialized) return\n initialized = true\n\n const level = levelFromEnv()\n const rates = samplingRatesForLevel(level)\n\n initLogger({\n env: { service: '@cipherstash/stack' },\n enabled: true,\n sampling: { rates },\n })\n}\n\n// Auto-init with defaults on first import\ninitStackLogger()\n\nexport { createRequestLogger }\n\n// Stringify only the first arg (the message string); drop subsequent args\n// which may contain sensitive objects (e.g. encryptConfig, plaintext).\nfunction safeMessage(args: unknown[]): string {\n return typeof args[0] === 'string' ? args[0] : ''\n}\n\n// Logger for simple one-off logs used across Stack interfaces.\nexport const logger = {\n debug(...args: unknown[]) {\n const log = createRequestLogger()\n log.set({\n level: 'debug',\n source: '@cipherstash/stack',\n message: safeMessage(args),\n })\n log.emit()\n },\n info(...args: unknown[]) {\n const log = createRequestLogger()\n log.set({ source: '@cipherstash/stack' })\n log.info(safeMessage(args))\n log.emit()\n },\n warn(...args: unknown[]) {\n const log = createRequestLogger()\n log.warn(safeMessage(args))\n log.emit()\n },\n error(...args: unknown[]) {\n const log = createRequestLogger()\n log.error(safeMessage(args))\n log.emit()\n },\n}\n"],"mappings":"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","names":["path","fs"]}
|
|
@@ -1,3 +1,79 @@
|
|
|
1
|
-
|
|
2
|
-
import '@byteslice/result';
|
|
1
|
+
import { EncryptionError } from '../errors/index.cjs';
|
|
2
|
+
import { Result } from '@byteslice/result';
|
|
3
3
|
import '@cipherstash/protect-ffi';
|
|
4
|
+
|
|
5
|
+
type CtsRegions = 'ap-southeast-2';
|
|
6
|
+
type IdentifyOptions = {
|
|
7
|
+
fetchFromCts?: boolean;
|
|
8
|
+
};
|
|
9
|
+
type CtsToken = {
|
|
10
|
+
accessToken: string;
|
|
11
|
+
expiry: number;
|
|
12
|
+
};
|
|
13
|
+
type Context = {
|
|
14
|
+
identityClaim: string[];
|
|
15
|
+
};
|
|
16
|
+
type LockContextOptions = {
|
|
17
|
+
context?: Context;
|
|
18
|
+
ctsToken?: CtsToken;
|
|
19
|
+
};
|
|
20
|
+
type GetLockContextResponse = {
|
|
21
|
+
ctsToken: CtsToken;
|
|
22
|
+
context: Context;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Manages CipherStash lock contexts for row-level access control.
|
|
26
|
+
*
|
|
27
|
+
* A `LockContext` ties encryption/decryption operations to an authenticated
|
|
28
|
+
* user identity via CTS (CipherStash Token Service). Call {@link identify}
|
|
29
|
+
* with a user's JWT to obtain a CTS token, then pass the `LockContext`
|
|
30
|
+
* to `.withLockContext()` on any encrypt/decrypt operation.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* import { LockContext } from "@cipherstash/stack/identity"
|
|
35
|
+
*
|
|
36
|
+
* const lc = new LockContext()
|
|
37
|
+
* const identified = await lc.identify(userJwt)
|
|
38
|
+
*
|
|
39
|
+
* if (identified.failure) throw new Error(identified.failure.message)
|
|
40
|
+
*
|
|
41
|
+
* const result = await client
|
|
42
|
+
* .encrypt(value, { column: users.email, table: users })
|
|
43
|
+
* .withLockContext(identified.data)
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
declare class LockContext {
|
|
47
|
+
private ctsToken;
|
|
48
|
+
private workspaceId;
|
|
49
|
+
private context;
|
|
50
|
+
constructor({ context, ctsToken, }?: LockContextOptions);
|
|
51
|
+
/**
|
|
52
|
+
* Exchange a user's JWT for a CTS token and bind it to this lock context.
|
|
53
|
+
*
|
|
54
|
+
* @param jwtToken - A valid OIDC / JWT token for the current user.
|
|
55
|
+
* @returns A `Result` containing this `LockContext` (now authenticated) or an error.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* const lc = new LockContext()
|
|
60
|
+
* const result = await lc.identify(userJwt)
|
|
61
|
+
* if (result.failure) {
|
|
62
|
+
* console.error("Auth failed:", result.failure.message)
|
|
63
|
+
* }
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
identify(jwtToken: string): Promise<Result<LockContext, EncryptionError>>;
|
|
67
|
+
/**
|
|
68
|
+
* Retrieve the current CTS token and context for use with encryption operations.
|
|
69
|
+
*
|
|
70
|
+
* Must be called after {@link identify}. Returns the token/context pair that
|
|
71
|
+
* `.withLockContext()` expects.
|
|
72
|
+
*
|
|
73
|
+
* @returns A `Result` containing the CTS token and identity context, or an error
|
|
74
|
+
* if {@link identify} has not been called.
|
|
75
|
+
*/
|
|
76
|
+
getLockContext(): Promise<Result<GetLockContextResponse, EncryptionError>>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export { type Context, type CtsRegions, type CtsToken, type GetLockContextResponse, type IdentifyOptions, LockContext, type LockContextOptions };
|
package/dist/identity/index.d.ts
CHANGED
|
@@ -1,3 +1,79 @@
|
|
|
1
|
-
|
|
2
|
-
import '@byteslice/result';
|
|
1
|
+
import { EncryptionError } from '../errors/index.js';
|
|
2
|
+
import { Result } from '@byteslice/result';
|
|
3
3
|
import '@cipherstash/protect-ffi';
|
|
4
|
+
|
|
5
|
+
type CtsRegions = 'ap-southeast-2';
|
|
6
|
+
type IdentifyOptions = {
|
|
7
|
+
fetchFromCts?: boolean;
|
|
8
|
+
};
|
|
9
|
+
type CtsToken = {
|
|
10
|
+
accessToken: string;
|
|
11
|
+
expiry: number;
|
|
12
|
+
};
|
|
13
|
+
type Context = {
|
|
14
|
+
identityClaim: string[];
|
|
15
|
+
};
|
|
16
|
+
type LockContextOptions = {
|
|
17
|
+
context?: Context;
|
|
18
|
+
ctsToken?: CtsToken;
|
|
19
|
+
};
|
|
20
|
+
type GetLockContextResponse = {
|
|
21
|
+
ctsToken: CtsToken;
|
|
22
|
+
context: Context;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Manages CipherStash lock contexts for row-level access control.
|
|
26
|
+
*
|
|
27
|
+
* A `LockContext` ties encryption/decryption operations to an authenticated
|
|
28
|
+
* user identity via CTS (CipherStash Token Service). Call {@link identify}
|
|
29
|
+
* with a user's JWT to obtain a CTS token, then pass the `LockContext`
|
|
30
|
+
* to `.withLockContext()` on any encrypt/decrypt operation.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* import { LockContext } from "@cipherstash/stack/identity"
|
|
35
|
+
*
|
|
36
|
+
* const lc = new LockContext()
|
|
37
|
+
* const identified = await lc.identify(userJwt)
|
|
38
|
+
*
|
|
39
|
+
* if (identified.failure) throw new Error(identified.failure.message)
|
|
40
|
+
*
|
|
41
|
+
* const result = await client
|
|
42
|
+
* .encrypt(value, { column: users.email, table: users })
|
|
43
|
+
* .withLockContext(identified.data)
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
declare class LockContext {
|
|
47
|
+
private ctsToken;
|
|
48
|
+
private workspaceId;
|
|
49
|
+
private context;
|
|
50
|
+
constructor({ context, ctsToken, }?: LockContextOptions);
|
|
51
|
+
/**
|
|
52
|
+
* Exchange a user's JWT for a CTS token and bind it to this lock context.
|
|
53
|
+
*
|
|
54
|
+
* @param jwtToken - A valid OIDC / JWT token for the current user.
|
|
55
|
+
* @returns A `Result` containing this `LockContext` (now authenticated) or an error.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* const lc = new LockContext()
|
|
60
|
+
* const result = await lc.identify(userJwt)
|
|
61
|
+
* if (result.failure) {
|
|
62
|
+
* console.error("Auth failed:", result.failure.message)
|
|
63
|
+
* }
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
identify(jwtToken: string): Promise<Result<LockContext, EncryptionError>>;
|
|
67
|
+
/**
|
|
68
|
+
* Retrieve the current CTS token and context for use with encryption operations.
|
|
69
|
+
*
|
|
70
|
+
* Must be called after {@link identify}. Returns the token/context pair that
|
|
71
|
+
* `.withLockContext()` expects.
|
|
72
|
+
*
|
|
73
|
+
* @returns A `Result` containing the CTS token and identity context, or an error
|
|
74
|
+
* if {@link identify} has not been called.
|
|
75
|
+
*/
|
|
76
|
+
getLockContext(): Promise<Result<GetLockContextResponse, EncryptionError>>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export { type Context, type CtsRegions, type CtsToken, type GetLockContextResponse, type IdentifyOptions, LockContext, type LockContextOptions };
|
package/dist/identity/index.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
loadWorkSpaceId
|
|
3
|
+
} from "../chunk-GXGEW6T4.js";
|
|
4
|
+
import {
|
|
4
5
|
logger
|
|
5
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-Q5FTQLYG.js";
|
|
7
|
+
import {
|
|
8
|
+
EncryptionErrorTypes
|
|
9
|
+
} from "../chunk-LBMC4D6D.js";
|
|
6
10
|
|
|
7
11
|
// src/identity/index.ts
|
|
8
12
|
import { withResult } from "@byteslice/result";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/identity/index.ts"],"sourcesContent":["import { type EncryptionError, EncryptionErrorTypes } from '@/errors'\nimport { loadWorkSpaceId } from '@/utils/config'\nimport { logger } from '@/utils/logger'\nimport { type Result, withResult } from '@byteslice/result'\n\nexport type CtsRegions = 'ap-southeast-2'\n\nexport type IdentifyOptions = {\n fetchFromCts?: boolean\n}\n\nexport type CtsToken = {\n accessToken: string\n expiry: number\n}\n\nexport type Context = {\n identityClaim: string[]\n}\n\nexport type LockContextOptions = {\n context?: Context\n ctsToken?: CtsToken\n}\n\nexport type GetLockContextResponse = {\n ctsToken: CtsToken\n context: Context\n}\n\n/**\n * Manages CipherStash lock contexts for row-level access control.\n *\n * A `LockContext` ties encryption/decryption operations to an authenticated\n * user identity via CTS (CipherStash Token Service). Call {@link identify}\n * with a user's JWT to obtain a CTS token, then pass the `LockContext`\n * to `.withLockContext()` on any encrypt/decrypt operation.\n *\n * @example\n * ```typescript\n * import { LockContext } from \"@cipherstash/stack/identity\"\n *\n * const lc = new LockContext()\n * const identified = await lc.identify(userJwt)\n *\n * if (identified.failure) throw new Error(identified.failure.message)\n *\n * const result = await client\n * .encrypt(value, { column: users.email, table: users })\n * .withLockContext(identified.data)\n * ```\n */\nexport class LockContext {\n private ctsToken: CtsToken | undefined\n private workspaceId: string\n private context: Context\n\n constructor({\n context = { identityClaim: ['sub'] },\n ctsToken,\n }: LockContextOptions = {}) {\n const workspaceId = loadWorkSpaceId()\n\n if (!workspaceId) {\n throw new Error(\n 'You have not defined a workspace ID in your config file, or the CS_WORKSPACE_CRN environment variable.',\n )\n }\n\n if (ctsToken) {\n this.ctsToken = ctsToken\n }\n\n this.workspaceId = workspaceId\n this.context = context\n logger.debug('Successfully initialized the EQL lock context.')\n }\n\n /**\n * Exchange a user's JWT for a CTS token and bind it to this lock context.\n *\n * @param jwtToken - A valid OIDC / JWT token for the current user.\n * @returns A `Result` containing this `LockContext` (now authenticated) or an error.\n *\n * @example\n * ```typescript\n * const lc = new LockContext()\n * const result = await lc.identify(userJwt)\n * if (result.failure) {\n * console.error(\"Auth failed:\", result.failure.message)\n * }\n * ```\n */\n async identify(\n jwtToken: string,\n ): Promise<Result<LockContext, EncryptionError>> {\n const workspaceId = this.workspaceId\n\n const ctsEndpoint =\n process.env.CS_CTS_ENDPOINT ||\n 'https://ap-southeast-2.aws.auth.viturhosted.net'\n\n const ctsFetchResult = await withResult(\n () =>\n fetch(`${ctsEndpoint}/api/authorize`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n workspaceId,\n oidcToken: jwtToken,\n }),\n }),\n (error) => ({\n type: EncryptionErrorTypes.CtsTokenError,\n message: error.message,\n }),\n )\n\n if (ctsFetchResult.failure) {\n return ctsFetchResult\n }\n\n const identifiedLockContext = await withResult(\n async () => {\n const ctsToken = (await ctsFetchResult.data.json()) as CtsToken\n\n if (!ctsToken.accessToken) {\n throw new Error(\n 'The response from the CipherStash API did not contain an access token. Please contact support.',\n )\n }\n\n this.ctsToken = ctsToken\n return this\n },\n (error) => ({\n type: EncryptionErrorTypes.CtsTokenError,\n message: error.message,\n }),\n )\n\n return identifiedLockContext\n }\n\n /**\n * Retrieve the current CTS token and context for use with encryption operations.\n *\n * Must be called after {@link identify}. Returns the token/context pair that\n * `.withLockContext()` expects.\n *\n * @returns A `Result` containing the CTS token and identity context, or an error\n * if {@link identify} has not been called.\n */\n getLockContext(): Promise<Result<GetLockContextResponse, EncryptionError>> {\n return withResult(\n () => {\n if (!this.ctsToken?.accessToken || !this.ctsToken?.expiry) {\n throw new Error(\n 'The CTS token is not set. Please call identify() with a users JWT token, or pass an existing CTS token to the LockContext constructor before calling getLockContext().',\n )\n }\n\n return {\n context: this.context,\n ctsToken: this.ctsToken,\n }\n },\n (error) => ({\n type: EncryptionErrorTypes.CtsTokenError,\n message: error.message,\n }),\n )\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../src/identity/index.ts"],"sourcesContent":["import { type EncryptionError, EncryptionErrorTypes } from '@/errors'\nimport { loadWorkSpaceId } from '@/utils/config'\nimport { logger } from '@/utils/logger'\nimport { type Result, withResult } from '@byteslice/result'\n\nexport type CtsRegions = 'ap-southeast-2'\n\nexport type IdentifyOptions = {\n fetchFromCts?: boolean\n}\n\nexport type CtsToken = {\n accessToken: string\n expiry: number\n}\n\nexport type Context = {\n identityClaim: string[]\n}\n\nexport type LockContextOptions = {\n context?: Context\n ctsToken?: CtsToken\n}\n\nexport type GetLockContextResponse = {\n ctsToken: CtsToken\n context: Context\n}\n\n/**\n * Manages CipherStash lock contexts for row-level access control.\n *\n * A `LockContext` ties encryption/decryption operations to an authenticated\n * user identity via CTS (CipherStash Token Service). Call {@link identify}\n * with a user's JWT to obtain a CTS token, then pass the `LockContext`\n * to `.withLockContext()` on any encrypt/decrypt operation.\n *\n * @example\n * ```typescript\n * import { LockContext } from \"@cipherstash/stack/identity\"\n *\n * const lc = new LockContext()\n * const identified = await lc.identify(userJwt)\n *\n * if (identified.failure) throw new Error(identified.failure.message)\n *\n * const result = await client\n * .encrypt(value, { column: users.email, table: users })\n * .withLockContext(identified.data)\n * ```\n */\nexport class LockContext {\n private ctsToken: CtsToken | undefined\n private workspaceId: string\n private context: Context\n\n constructor({\n context = { identityClaim: ['sub'] },\n ctsToken,\n }: LockContextOptions = {}) {\n const workspaceId = loadWorkSpaceId()\n\n if (!workspaceId) {\n throw new Error(\n 'You have not defined a workspace ID in your config file, or the CS_WORKSPACE_CRN environment variable.',\n )\n }\n\n if (ctsToken) {\n this.ctsToken = ctsToken\n }\n\n this.workspaceId = workspaceId\n this.context = context\n logger.debug('Successfully initialized the EQL lock context.')\n }\n\n /**\n * Exchange a user's JWT for a CTS token and bind it to this lock context.\n *\n * @param jwtToken - A valid OIDC / JWT token for the current user.\n * @returns A `Result` containing this `LockContext` (now authenticated) or an error.\n *\n * @example\n * ```typescript\n * const lc = new LockContext()\n * const result = await lc.identify(userJwt)\n * if (result.failure) {\n * console.error(\"Auth failed:\", result.failure.message)\n * }\n * ```\n */\n async identify(\n jwtToken: string,\n ): Promise<Result<LockContext, EncryptionError>> {\n const workspaceId = this.workspaceId\n\n const ctsEndpoint =\n process.env.CS_CTS_ENDPOINT ||\n 'https://ap-southeast-2.aws.auth.viturhosted.net'\n\n const ctsFetchResult = await withResult(\n () =>\n fetch(`${ctsEndpoint}/api/authorize`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n workspaceId,\n oidcToken: jwtToken,\n }),\n }),\n (error) => ({\n type: EncryptionErrorTypes.CtsTokenError,\n message: error.message,\n }),\n )\n\n if (ctsFetchResult.failure) {\n return ctsFetchResult\n }\n\n const identifiedLockContext = await withResult(\n async () => {\n const ctsToken = (await ctsFetchResult.data.json()) as CtsToken\n\n if (!ctsToken.accessToken) {\n throw new Error(\n 'The response from the CipherStash API did not contain an access token. Please contact support.',\n )\n }\n\n this.ctsToken = ctsToken\n return this\n },\n (error) => ({\n type: EncryptionErrorTypes.CtsTokenError,\n message: error.message,\n }),\n )\n\n return identifiedLockContext\n }\n\n /**\n * Retrieve the current CTS token and context for use with encryption operations.\n *\n * Must be called after {@link identify}. Returns the token/context pair that\n * `.withLockContext()` expects.\n *\n * @returns A `Result` containing the CTS token and identity context, or an error\n * if {@link identify} has not been called.\n */\n getLockContext(): Promise<Result<GetLockContextResponse, EncryptionError>> {\n return withResult(\n () => {\n if (!this.ctsToken?.accessToken || !this.ctsToken?.expiry) {\n throw new Error(\n 'The CTS token is not set. Please call identify() with a users JWT token, or pass an existing CTS token to the LockContext constructor before calling getLockContext().',\n )\n }\n\n return {\n context: this.context,\n ctsToken: this.ctsToken,\n }\n },\n (error) => ({\n type: EncryptionErrorTypes.CtsTokenError,\n message: error.message,\n }),\n )\n }\n}\n"],"mappings":";;;;;;;;;;;AAGA,SAAsB,kBAAkB;AAiDjC,IAAM,cAAN,MAAkB;AAAA,EACf;AAAA,EACA;AAAA,EACA;AAAA,EAER,YAAY;AAAA,IACV,UAAU,EAAE,eAAe,CAAC,KAAK,EAAE;AAAA,IACnC;AAAA,EACF,IAAwB,CAAC,GAAG;AAC1B,UAAM,cAAc,gBAAgB;AAEpC,QAAI,CAAC,aAAa;AAChB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,QAAI,UAAU;AACZ,WAAK,WAAW;AAAA,IAClB;AAEA,SAAK,cAAc;AACnB,SAAK,UAAU;AACf,WAAO,MAAM,gDAAgD;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,MAAM,SACJ,UAC+C;AAC/C,UAAM,cAAc,KAAK;AAEzB,UAAM,cACJ,QAAQ,IAAI,mBACZ;AAEF,UAAM,iBAAiB,MAAM;AAAA,MAC3B,MACE,MAAM,GAAG,WAAW,kBAAkB;AAAA,QACpC,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,QAClB;AAAA,QACA,MAAM,KAAK,UAAU;AAAA,UACnB;AAAA,UACA,WAAW;AAAA,QACb,CAAC;AAAA,MACH,CAAC;AAAA,MACH,CAAC,WAAW;AAAA,QACV,MAAM,qBAAqB;AAAA,QAC3B,SAAS,MAAM;AAAA,MACjB;AAAA,IACF;AAEA,QAAI,eAAe,SAAS;AAC1B,aAAO;AAAA,IACT;AAEA,UAAM,wBAAwB,MAAM;AAAA,MAClC,YAAY;AACV,cAAM,WAAY,MAAM,eAAe,KAAK,KAAK;AAEjD,YAAI,CAAC,SAAS,aAAa;AACzB,gBAAM,IAAI;AAAA,YACR;AAAA,UACF;AAAA,QACF;AAEA,aAAK,WAAW;AAChB,eAAO;AAAA,MACT;AAAA,MACA,CAAC,WAAW;AAAA,QACV,MAAM,qBAAqB;AAAA,QAC3B,SAAS,MAAM;AAAA,MACjB;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,iBAA2E;AACzE,WAAO;AAAA,MACL,MAAM;AACJ,YAAI,CAAC,KAAK,UAAU,eAAe,CAAC,KAAK,UAAU,QAAQ;AACzD,gBAAM,IAAI;AAAA,YACR;AAAA,UACF;AAAA,QACF;AAEA,eAAO;AAAA,UACL,SAAS,KAAK;AAAA,UACd,UAAU,KAAK;AAAA,QACjB;AAAA,MACF;AAAA,MACA,CAAC,WAAW;AAAA,QACV,MAAM,qBAAqB;AAAA,QAC3B,SAAS,MAAM;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|