@getstrata/core 2.2.11 → 2.2.13
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 +10 -0
- package/README.md +1 -1
- package/dist/core/crypto/fieldEncryption.d.ts +29 -0
- package/dist/core/crypto/fieldEncryptionRotation.d.ts +34 -0
- package/dist/core/view/index.d.ts +4 -2
- package/dist/core/view/viewRenderer.d.ts +24 -0
- package/dist/core/view/webLayoutData.d.ts +3 -1
- package/dist/entries/auth/basicAuthGuard.js +154 -1
- package/dist/entries/auth/passwordLogin.js +154 -1
- package/dist/entries/crypto/fieldEncryption.js +145 -1
- package/dist/entries/crypto/fieldEncryptionRotation.js +1 -0
- package/dist/entries/crypto/mfaSecret.js +154 -1
- package/dist/entries/database/schema.js +2 -2
- package/dist/framework/public-api.d.ts +4 -1
- package/dist/index.js +3102 -2705
- package/package.json +8 -3
|
@@ -26,7 +26,7 @@ function isFieldEncryptionEnabled() {
|
|
|
26
26
|
if (featureFlag === "true") {
|
|
27
27
|
return true;
|
|
28
28
|
}
|
|
29
|
-
return Boolean(resolveEncryptionKey());
|
|
29
|
+
return Boolean(resolveFieldEncryptionKeyring() || resolveEncryptionKey());
|
|
30
30
|
}
|
|
31
31
|
function encryptField(plaintext, key) {
|
|
32
32
|
const iv = randomBytes(IV_LENGTH);
|
|
@@ -56,6 +56,13 @@ function normalizeEmail(email) {
|
|
|
56
56
|
}
|
|
57
57
|
function protectEmail(email) {
|
|
58
58
|
const normalized = normalizeEmail(email);
|
|
59
|
+
const configured = resolveFieldEncryptionKeyring();
|
|
60
|
+
if (configured && isFieldEncryptionEnabled()) {
|
|
61
|
+
return {
|
|
62
|
+
storedEmail: configured.encrypt(normalized, "email"),
|
|
63
|
+
emailLookup: configured.keyring.lookup(normalized)
|
|
64
|
+
};
|
|
65
|
+
}
|
|
59
66
|
const key = resolveEncryptionKey();
|
|
60
67
|
if (!key || !isFieldEncryptionEnabled()) {
|
|
61
68
|
return { storedEmail: normalized, emailLookup: normalized };
|
|
@@ -66,6 +73,11 @@ function protectEmail(email) {
|
|
|
66
73
|
};
|
|
67
74
|
}
|
|
68
75
|
function revealEmail(storedEmail) {
|
|
76
|
+
const configured = resolveFieldEncryptionKeyring();
|
|
77
|
+
if (configured && storedEmail.startsWith("enc:"))
|
|
78
|
+
return configured.keyring.decrypt(storedEmail, "email");
|
|
79
|
+
if (storedEmail.startsWith("enc:") && !storedEmail.startsWith(ENCRYPTION_PREFIX))
|
|
80
|
+
throw new Error("Identified encrypted fields require KMS_ENCRYPTION_KEYRING.");
|
|
69
81
|
const key = resolveEncryptionKey();
|
|
70
82
|
if (!key || !storedEmail.startsWith(ENCRYPTION_PREFIX)) {
|
|
71
83
|
return storedEmail;
|
|
@@ -74,13 +86,144 @@ function revealEmail(storedEmail) {
|
|
|
74
86
|
}
|
|
75
87
|
function emailLookupForQuery(email) {
|
|
76
88
|
const normalized = normalizeEmail(email);
|
|
89
|
+
const configured = resolveFieldEncryptionKeyring();
|
|
90
|
+
if (configured && isFieldEncryptionEnabled())
|
|
91
|
+
return configured.keyring.lookup(normalized);
|
|
77
92
|
const key = resolveEncryptionKey();
|
|
78
93
|
if (!key || !isFieldEncryptionEnabled()) {
|
|
79
94
|
return normalized;
|
|
80
95
|
}
|
|
81
96
|
return hashLookupValue(normalized, key);
|
|
82
97
|
}
|
|
98
|
+
function createFieldEncryptionKeyring(options) {
|
|
99
|
+
const identifier = /^[A-Za-z0-9_-]{1,64}$/;
|
|
100
|
+
const copyKey = (value) => {
|
|
101
|
+
if (!(value instanceof Uint8Array) || value.byteLength !== 32)
|
|
102
|
+
throw new TypeError("Field encryption keys must be 32 bytes.");
|
|
103
|
+
return Buffer.from(value);
|
|
104
|
+
};
|
|
105
|
+
const entries = Object.entries(options.encryptionKeys);
|
|
106
|
+
if (entries.length < 1 || entries.length > 32)
|
|
107
|
+
throw new TypeError("Field encryption keyrings require 1 to 32 keys.");
|
|
108
|
+
const keys = new Map;
|
|
109
|
+
for (const [id, key] of entries) {
|
|
110
|
+
if (!identifier.test(id))
|
|
111
|
+
throw new TypeError("Invalid field encryption key identifier.");
|
|
112
|
+
keys.set(id, copyKey(key));
|
|
113
|
+
}
|
|
114
|
+
const activeKeyId = options.activeKeyId;
|
|
115
|
+
const active = keys.get(activeKeyId);
|
|
116
|
+
if (!active)
|
|
117
|
+
throw new TypeError("Active field encryption key is not retained.");
|
|
118
|
+
const legacy = options.legacyV1Key === undefined ? undefined : copyKey(options.legacyV1Key);
|
|
119
|
+
const lookup = copyKey(options.lookupKey);
|
|
120
|
+
const associatedData = (id, purpose) => {
|
|
121
|
+
if (!identifier.test(purpose))
|
|
122
|
+
throw new TypeError("Invalid field encryption purpose.");
|
|
123
|
+
return Buffer.from(`strata:field:v2:${id}:${purpose}`, "utf8");
|
|
124
|
+
};
|
|
125
|
+
const decodePayload = (encoded) => {
|
|
126
|
+
const payload = Buffer.from(encoded, "base64");
|
|
127
|
+
if (payload.length < IV_LENGTH + TAG_LENGTH || payload.toString("base64") !== encoded)
|
|
128
|
+
throw new Error("Invalid encrypted field.");
|
|
129
|
+
return payload;
|
|
130
|
+
};
|
|
131
|
+
return Object.freeze({
|
|
132
|
+
activeKeyId,
|
|
133
|
+
encrypt(plaintext, purpose) {
|
|
134
|
+
const aad = associatedData(activeKeyId, purpose);
|
|
135
|
+
const iv = randomBytes(IV_LENGTH);
|
|
136
|
+
const cipher = createCipheriv("aes-256-gcm", active, iv, { authTagLength: TAG_LENGTH });
|
|
137
|
+
cipher.setAAD(aad);
|
|
138
|
+
const ciphertext = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
139
|
+
const payload = Buffer.concat([iv, ciphertext, cipher.getAuthTag()]).toString("base64");
|
|
140
|
+
return `enc:v2:${activeKeyId}:${payload}`;
|
|
141
|
+
},
|
|
142
|
+
decrypt(value, purpose) {
|
|
143
|
+
associatedData(activeKeyId, purpose);
|
|
144
|
+
try {
|
|
145
|
+
if (value.startsWith(ENCRYPTION_PREFIX)) {
|
|
146
|
+
if (!legacy)
|
|
147
|
+
throw new Error("Missing legacy key");
|
|
148
|
+
decodePayload(value.slice(ENCRYPTION_PREFIX.length));
|
|
149
|
+
return decryptField(value, legacy);
|
|
150
|
+
}
|
|
151
|
+
const parts = value.split(":");
|
|
152
|
+
if (parts.length !== 4 || parts[0] !== "enc" || parts[1] !== "v2")
|
|
153
|
+
throw new Error("Invalid envelope");
|
|
154
|
+
const id = parts[2] ?? "";
|
|
155
|
+
const key = keys.get(id);
|
|
156
|
+
if (!key)
|
|
157
|
+
throw new Error("Missing retained key");
|
|
158
|
+
const payload = decodePayload(parts[3] ?? "");
|
|
159
|
+
const decipher = createDecipheriv("aes-256-gcm", key, payload.subarray(0, IV_LENGTH), {
|
|
160
|
+
authTagLength: TAG_LENGTH
|
|
161
|
+
});
|
|
162
|
+
decipher.setAAD(associatedData(id, purpose));
|
|
163
|
+
decipher.setAuthTag(payload.subarray(payload.length - TAG_LENGTH));
|
|
164
|
+
return Buffer.concat([
|
|
165
|
+
decipher.update(payload.subarray(IV_LENGTH, payload.length - TAG_LENGTH)),
|
|
166
|
+
decipher.final()
|
|
167
|
+
]).toString("utf8");
|
|
168
|
+
} catch {
|
|
169
|
+
throw new Error("Unable to decrypt encrypted field.");
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
lookup(normalizedValue) {
|
|
173
|
+
return hashLookupValue(normalizedValue, lookup);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
function resolveFieldEncryptionKeyring(env = process.env) {
|
|
178
|
+
const raw = env.KMS_ENCRYPTION_KEYRING;
|
|
179
|
+
if (raw === undefined)
|
|
180
|
+
return null;
|
|
181
|
+
try {
|
|
182
|
+
if (!raw.trim() || raw.length > 16384 || env.KMS_ENCRYPTION_KEY?.trim())
|
|
183
|
+
throw new Error("Invalid configuration");
|
|
184
|
+
const parsed = JSON.parse(raw);
|
|
185
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
186
|
+
throw new Error("Invalid configuration");
|
|
187
|
+
const config = parsed;
|
|
188
|
+
if (Object.keys(config).some((key) => !["activeKeyId", "encryptionKeys", "legacyV1Key", "lookupKey", "writeVersion"].includes(key)))
|
|
189
|
+
throw new Error("Unknown option");
|
|
190
|
+
if (typeof config.activeKeyId !== "string" || config.writeVersion !== 1 && config.writeVersion !== 2 || !config.encryptionKeys || typeof config.encryptionKeys !== "object" || Array.isArray(config.encryptionKeys))
|
|
191
|
+
throw new Error("Invalid configuration");
|
|
192
|
+
const decodeKey = (value) => {
|
|
193
|
+
if (typeof value !== "string")
|
|
194
|
+
throw new Error("Invalid key");
|
|
195
|
+
if (/^[0-9a-f]{64}$/i.test(value))
|
|
196
|
+
return Buffer.from(value, "hex");
|
|
197
|
+
const decoded = Buffer.from(value, "base64");
|
|
198
|
+
if (decoded.length !== 32 || decoded.toString("base64") !== value)
|
|
199
|
+
throw new Error("Invalid key");
|
|
200
|
+
return decoded;
|
|
201
|
+
};
|
|
202
|
+
const keys = Object.fromEntries(Object.entries(config.encryptionKeys).map(([id, value]) => [id, decodeKey(value)]));
|
|
203
|
+
const legacy = config.legacyV1Key === undefined ? undefined : decodeKey(config.legacyV1Key);
|
|
204
|
+
const lookup = decodeKey(config.lookupKey);
|
|
205
|
+
if (config.writeVersion === 1 && !legacy || legacy && !legacy.equals(lookup))
|
|
206
|
+
throw new Error("Incompatible lookup key");
|
|
207
|
+
const keyring = createFieldEncryptionKeyring({
|
|
208
|
+
activeKeyId: config.activeKeyId,
|
|
209
|
+
encryptionKeys: keys,
|
|
210
|
+
legacyV1Key: legacy,
|
|
211
|
+
lookupKey: lookup
|
|
212
|
+
});
|
|
213
|
+
const writeVersion = config.writeVersion;
|
|
214
|
+
return Object.freeze({
|
|
215
|
+
keyring,
|
|
216
|
+
writeVersion,
|
|
217
|
+
encrypt(plaintext, purpose) {
|
|
218
|
+
return writeVersion === 1 && legacy ? encryptField(plaintext, legacy) : keyring.encrypt(plaintext, purpose);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
} catch {
|
|
222
|
+
throw new Error("Invalid KMS_ENCRYPTION_KEYRING configuration.");
|
|
223
|
+
}
|
|
224
|
+
}
|
|
83
225
|
export {
|
|
226
|
+
createFieldEncryptionKeyring,
|
|
84
227
|
decryptField,
|
|
85
228
|
emailLookupForQuery,
|
|
86
229
|
encryptField,
|
|
@@ -89,5 +232,6 @@ export {
|
|
|
89
232
|
normalizeEmail,
|
|
90
233
|
protectEmail,
|
|
91
234
|
resolveEncryptionKey,
|
|
235
|
+
resolveFieldEncryptionKeyring,
|
|
92
236
|
revealEmail
|
|
93
237
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../../index.js";
|
|
@@ -46,7 +46,7 @@ function isFieldEncryptionEnabled() {
|
|
|
46
46
|
if (featureFlag === "true") {
|
|
47
47
|
return true;
|
|
48
48
|
}
|
|
49
|
-
return Boolean(resolveEncryptionKey());
|
|
49
|
+
return Boolean(resolveFieldEncryptionKeyring() || resolveEncryptionKey());
|
|
50
50
|
}
|
|
51
51
|
function encryptField(plaintext, key) {
|
|
52
52
|
const iv = randomBytes(IV_LENGTH);
|
|
@@ -76,6 +76,13 @@ function normalizeEmail(email) {
|
|
|
76
76
|
}
|
|
77
77
|
function protectEmail(email) {
|
|
78
78
|
const normalized = normalizeEmail(email);
|
|
79
|
+
const configured = resolveFieldEncryptionKeyring();
|
|
80
|
+
if (configured && isFieldEncryptionEnabled()) {
|
|
81
|
+
return {
|
|
82
|
+
storedEmail: configured.encrypt(normalized, "email"),
|
|
83
|
+
emailLookup: configured.keyring.lookup(normalized)
|
|
84
|
+
};
|
|
85
|
+
}
|
|
79
86
|
const key = resolveEncryptionKey();
|
|
80
87
|
if (!key || !isFieldEncryptionEnabled()) {
|
|
81
88
|
return { storedEmail: normalized, emailLookup: normalized };
|
|
@@ -86,6 +93,11 @@ function protectEmail(email) {
|
|
|
86
93
|
};
|
|
87
94
|
}
|
|
88
95
|
function revealEmail(storedEmail) {
|
|
96
|
+
const configured = resolveFieldEncryptionKeyring();
|
|
97
|
+
if (configured && storedEmail.startsWith("enc:"))
|
|
98
|
+
return configured.keyring.decrypt(storedEmail, "email");
|
|
99
|
+
if (storedEmail.startsWith("enc:") && !storedEmail.startsWith(ENCRYPTION_PREFIX))
|
|
100
|
+
throw new Error("Identified encrypted fields require KMS_ENCRYPTION_KEYRING.");
|
|
89
101
|
const key = resolveEncryptionKey();
|
|
90
102
|
if (!key || !storedEmail.startsWith(ENCRYPTION_PREFIX)) {
|
|
91
103
|
return storedEmail;
|
|
@@ -94,15 +106,148 @@ function revealEmail(storedEmail) {
|
|
|
94
106
|
}
|
|
95
107
|
function emailLookupForQuery(email) {
|
|
96
108
|
const normalized = normalizeEmail(email);
|
|
109
|
+
const configured = resolveFieldEncryptionKeyring();
|
|
110
|
+
if (configured && isFieldEncryptionEnabled())
|
|
111
|
+
return configured.keyring.lookup(normalized);
|
|
97
112
|
const key = resolveEncryptionKey();
|
|
98
113
|
if (!key || !isFieldEncryptionEnabled()) {
|
|
99
114
|
return normalized;
|
|
100
115
|
}
|
|
101
116
|
return hashLookupValue(normalized, key);
|
|
102
117
|
}
|
|
118
|
+
function createFieldEncryptionKeyring(options) {
|
|
119
|
+
const identifier = /^[A-Za-z0-9_-]{1,64}$/;
|
|
120
|
+
const copyKey = (value) => {
|
|
121
|
+
if (!(value instanceof Uint8Array) || value.byteLength !== 32)
|
|
122
|
+
throw new TypeError("Field encryption keys must be 32 bytes.");
|
|
123
|
+
return Buffer.from(value);
|
|
124
|
+
};
|
|
125
|
+
const entries = Object.entries(options.encryptionKeys);
|
|
126
|
+
if (entries.length < 1 || entries.length > 32)
|
|
127
|
+
throw new TypeError("Field encryption keyrings require 1 to 32 keys.");
|
|
128
|
+
const keys = new Map;
|
|
129
|
+
for (const [id, key] of entries) {
|
|
130
|
+
if (!identifier.test(id))
|
|
131
|
+
throw new TypeError("Invalid field encryption key identifier.");
|
|
132
|
+
keys.set(id, copyKey(key));
|
|
133
|
+
}
|
|
134
|
+
const activeKeyId = options.activeKeyId;
|
|
135
|
+
const active = keys.get(activeKeyId);
|
|
136
|
+
if (!active)
|
|
137
|
+
throw new TypeError("Active field encryption key is not retained.");
|
|
138
|
+
const legacy = options.legacyV1Key === undefined ? undefined : copyKey(options.legacyV1Key);
|
|
139
|
+
const lookup = copyKey(options.lookupKey);
|
|
140
|
+
const associatedData = (id, purpose) => {
|
|
141
|
+
if (!identifier.test(purpose))
|
|
142
|
+
throw new TypeError("Invalid field encryption purpose.");
|
|
143
|
+
return Buffer.from(`strata:field:v2:${id}:${purpose}`, "utf8");
|
|
144
|
+
};
|
|
145
|
+
const decodePayload = (encoded) => {
|
|
146
|
+
const payload = Buffer.from(encoded, "base64");
|
|
147
|
+
if (payload.length < IV_LENGTH + TAG_LENGTH || payload.toString("base64") !== encoded)
|
|
148
|
+
throw new Error("Invalid encrypted field.");
|
|
149
|
+
return payload;
|
|
150
|
+
};
|
|
151
|
+
return Object.freeze({
|
|
152
|
+
activeKeyId,
|
|
153
|
+
encrypt(plaintext, purpose) {
|
|
154
|
+
const aad = associatedData(activeKeyId, purpose);
|
|
155
|
+
const iv = randomBytes(IV_LENGTH);
|
|
156
|
+
const cipher = createCipheriv("aes-256-gcm", active, iv, { authTagLength: TAG_LENGTH });
|
|
157
|
+
cipher.setAAD(aad);
|
|
158
|
+
const ciphertext = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
159
|
+
const payload = Buffer.concat([iv, ciphertext, cipher.getAuthTag()]).toString("base64");
|
|
160
|
+
return `enc:v2:${activeKeyId}:${payload}`;
|
|
161
|
+
},
|
|
162
|
+
decrypt(value, purpose) {
|
|
163
|
+
associatedData(activeKeyId, purpose);
|
|
164
|
+
try {
|
|
165
|
+
if (value.startsWith(ENCRYPTION_PREFIX)) {
|
|
166
|
+
if (!legacy)
|
|
167
|
+
throw new Error("Missing legacy key");
|
|
168
|
+
decodePayload(value.slice(ENCRYPTION_PREFIX.length));
|
|
169
|
+
return decryptField(value, legacy);
|
|
170
|
+
}
|
|
171
|
+
const parts = value.split(":");
|
|
172
|
+
if (parts.length !== 4 || parts[0] !== "enc" || parts[1] !== "v2")
|
|
173
|
+
throw new Error("Invalid envelope");
|
|
174
|
+
const id = parts[2] ?? "";
|
|
175
|
+
const key = keys.get(id);
|
|
176
|
+
if (!key)
|
|
177
|
+
throw new Error("Missing retained key");
|
|
178
|
+
const payload = decodePayload(parts[3] ?? "");
|
|
179
|
+
const decipher = createDecipheriv("aes-256-gcm", key, payload.subarray(0, IV_LENGTH), {
|
|
180
|
+
authTagLength: TAG_LENGTH
|
|
181
|
+
});
|
|
182
|
+
decipher.setAAD(associatedData(id, purpose));
|
|
183
|
+
decipher.setAuthTag(payload.subarray(payload.length - TAG_LENGTH));
|
|
184
|
+
return Buffer.concat([
|
|
185
|
+
decipher.update(payload.subarray(IV_LENGTH, payload.length - TAG_LENGTH)),
|
|
186
|
+
decipher.final()
|
|
187
|
+
]).toString("utf8");
|
|
188
|
+
} catch {
|
|
189
|
+
throw new Error("Unable to decrypt encrypted field.");
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
lookup(normalizedValue) {
|
|
193
|
+
return hashLookupValue(normalizedValue, lookup);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
function resolveFieldEncryptionKeyring(env = process.env) {
|
|
198
|
+
const raw = env.KMS_ENCRYPTION_KEYRING;
|
|
199
|
+
if (raw === undefined)
|
|
200
|
+
return null;
|
|
201
|
+
try {
|
|
202
|
+
if (!raw.trim() || raw.length > 16384 || env.KMS_ENCRYPTION_KEY?.trim())
|
|
203
|
+
throw new Error("Invalid configuration");
|
|
204
|
+
const parsed = JSON.parse(raw);
|
|
205
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
206
|
+
throw new Error("Invalid configuration");
|
|
207
|
+
const config = parsed;
|
|
208
|
+
if (Object.keys(config).some((key) => !["activeKeyId", "encryptionKeys", "legacyV1Key", "lookupKey", "writeVersion"].includes(key)))
|
|
209
|
+
throw new Error("Unknown option");
|
|
210
|
+
if (typeof config.activeKeyId !== "string" || config.writeVersion !== 1 && config.writeVersion !== 2 || !config.encryptionKeys || typeof config.encryptionKeys !== "object" || Array.isArray(config.encryptionKeys))
|
|
211
|
+
throw new Error("Invalid configuration");
|
|
212
|
+
const decodeKey = (value) => {
|
|
213
|
+
if (typeof value !== "string")
|
|
214
|
+
throw new Error("Invalid key");
|
|
215
|
+
if (/^[0-9a-f]{64}$/i.test(value))
|
|
216
|
+
return Buffer.from(value, "hex");
|
|
217
|
+
const decoded = Buffer.from(value, "base64");
|
|
218
|
+
if (decoded.length !== 32 || decoded.toString("base64") !== value)
|
|
219
|
+
throw new Error("Invalid key");
|
|
220
|
+
return decoded;
|
|
221
|
+
};
|
|
222
|
+
const keys = Object.fromEntries(Object.entries(config.encryptionKeys).map(([id, value]) => [id, decodeKey(value)]));
|
|
223
|
+
const legacy = config.legacyV1Key === undefined ? undefined : decodeKey(config.legacyV1Key);
|
|
224
|
+
const lookup = decodeKey(config.lookupKey);
|
|
225
|
+
if (config.writeVersion === 1 && !legacy || legacy && !legacy.equals(lookup))
|
|
226
|
+
throw new Error("Incompatible lookup key");
|
|
227
|
+
const keyring = createFieldEncryptionKeyring({
|
|
228
|
+
activeKeyId: config.activeKeyId,
|
|
229
|
+
encryptionKeys: keys,
|
|
230
|
+
legacyV1Key: legacy,
|
|
231
|
+
lookupKey: lookup
|
|
232
|
+
});
|
|
233
|
+
const writeVersion = config.writeVersion;
|
|
234
|
+
return Object.freeze({
|
|
235
|
+
keyring,
|
|
236
|
+
writeVersion,
|
|
237
|
+
encrypt(plaintext, purpose) {
|
|
238
|
+
return writeVersion === 1 && legacy ? encryptField(plaintext, legacy) : keyring.encrypt(plaintext, purpose);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
} catch {
|
|
242
|
+
throw new Error("Invalid KMS_ENCRYPTION_KEYRING configuration.");
|
|
243
|
+
}
|
|
244
|
+
}
|
|
103
245
|
|
|
104
246
|
// ../../src/core/crypto/mfaSecret.ts
|
|
105
247
|
function protectMfaSecret(secret) {
|
|
248
|
+
const configured = resolveFieldEncryptionKeyring();
|
|
249
|
+
if (configured)
|
|
250
|
+
return configured.encrypt(secret, "mfa");
|
|
106
251
|
const key = resolveEncryptionKey();
|
|
107
252
|
if (!key) {
|
|
108
253
|
throw new Error("MFA secrets require KMS_ENCRYPTION_KEY.");
|
|
@@ -113,6 +258,14 @@ function revealMfaSecret(stored) {
|
|
|
113
258
|
if (!stored) {
|
|
114
259
|
return null;
|
|
115
260
|
}
|
|
261
|
+
const configured = resolveFieldEncryptionKeyring();
|
|
262
|
+
if (configured) {
|
|
263
|
+
if (!stored.startsWith("enc:"))
|
|
264
|
+
throw new Error("MFA secrets must be encrypted.");
|
|
265
|
+
return configured.keyring.decrypt(stored, "mfa");
|
|
266
|
+
}
|
|
267
|
+
if (stored.startsWith("enc:") && !stored.startsWith("enc:v1:"))
|
|
268
|
+
throw new Error("Identified MFA secrets require KMS_ENCRYPTION_KEYRING.");
|
|
116
269
|
if (!stored.startsWith("enc:v1:")) {
|
|
117
270
|
if (isProductionEnv() || resolveEncryptionKey()) {
|
|
118
271
|
throw new Error("MFA secrets must be encrypted.");
|
|
@@ -694,10 +694,10 @@ function compileColumn(driver, column, mode) {
|
|
|
694
694
|
parts[1] = `${parts[1]} AUTO_INCREMENT`;
|
|
695
695
|
}
|
|
696
696
|
if (column.isPrimary && mode === "create") {
|
|
697
|
-
if (driver === "sqlite") {
|
|
697
|
+
if (driver === "sqlite" && column.autoIncrement) {
|
|
698
698
|
parts.push("PRIMARY KEY AUTOINCREMENT");
|
|
699
699
|
} else {
|
|
700
|
-
parts.push("PRIMARY KEY");
|
|
700
|
+
parts.push(driver === "sqlite" && !column.isNullable ? "PRIMARY KEY NOT NULL" : "PRIMARY KEY");
|
|
701
701
|
}
|
|
702
702
|
} else if (!column.isNullable) {
|
|
703
703
|
parts.push("NOT NULL");
|
|
@@ -32,6 +32,8 @@ export { CACHE_TAGS } from "../core/cache/tags.ts";
|
|
|
32
32
|
export { ConfigStore, createServiceToken, ServiceContainer, type ServiceToken, } from "../core/contracts/container.ts";
|
|
33
33
|
export type { ServiceProvider } from "../core/contracts/di.ts";
|
|
34
34
|
export { resolveService } from "../core/contracts/di.ts";
|
|
35
|
+
export type { FieldEncryptionRotationBatch, FieldEncryptionRotationOptions, } from "../core/crypto/fieldEncryptionRotation";
|
|
36
|
+
export { createFieldEncryptionRotationMigration, runFieldEncryptionRotationBatch, } from "../core/crypto/fieldEncryptionRotation";
|
|
35
37
|
export type { TransactionAcquisitionMetrics, TransactionAcquisitionSnapshot, } from "../core/database/acquisitionMetrics.ts";
|
|
36
38
|
export { createTransactionAcquisitionMetrics, renderTransactionAcquisitionMetrics, } from "../core/database/acquisitionMetrics.ts";
|
|
37
39
|
export type { DatabaseConnection } from "../core/database/baseRepository.ts";
|
|
@@ -167,6 +169,7 @@ export type { TracingOptions, TracingRuntime } from "../core/tracing/tracingMidd
|
|
|
167
169
|
export { acquireTracingRuntime, createTracingMiddleware, createTracingRuntime, getTracingRuntime, } from "../core/tracing/tracingMiddleware.ts";
|
|
168
170
|
export type { ValidationRule, ValidationSchema } from "../core/validation/rules.ts";
|
|
169
171
|
export { emailRule, maxLength, minLength, required, stringRule, validateObject, } from "../core/validation/rules.ts";
|
|
170
|
-
export { configureWebErrorView, configureWebLayoutData, DEFAULT_VIEWS_DIRECTORY, EtaViewEngine, errorTemplateName, htmlErrorResponse, htmlResponse, isHtmxRequest, notFoundHtmlResponse, redirectResponse, renderKernelErrorChrome, renderWebErrorHtml, resolveWebLayoutData, rssResponse, textResponse, xmlResponse, } from "../core/view/index.ts";
|
|
172
|
+
export { CORE_VIEW_RENDERER_TOKEN, configureWebErrorView, configureWebLayoutData, createWebLayoutDataResolver, DEFAULT_VIEWS_DIRECTORY, EtaViewEngine, errorTemplateName, htmlErrorResponse, htmlResponse, isHtmxRequest, notFoundHtmlResponse, redirectResponse, renderKernelErrorChrome, renderWebErrorHtml, resolveWebLayoutData, rssResponse, textResponse, ViewRenderer, xmlResponse, } from "../core/view/index.ts";
|
|
171
173
|
export type { ViewEngine } from "../core/view/viewEngine.ts";
|
|
174
|
+
export type { HtmxViewResponseOptions, ViewResponseOptions } from "../core/view/viewRenderer.ts";
|
|
172
175
|
export type { WebLayoutAuthUser, WebLayoutData, WebLayoutDataOptions, WebLayoutUserKey, } from "../core/view/webLayoutData.ts";
|