@dereekb/firebase-server 13.1.0 → 13.2.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/index.cjs.js +137 -0
- package/index.cjs.js.map +1 -1
- package/index.esm.js +137 -2
- package/index.esm.js.map +1 -1
- package/mailgun/package.json +8 -8
- package/model/package.json +8 -8
- package/package.json +9 -9
- package/src/lib/firestore/index.d.ts +1 -0
- package/src/lib/firestore/snapshot/index.d.ts +1 -0
- package/src/lib/firestore/snapshot/snapshot.field.d.ts +80 -0
- package/test/package.json +8 -8
- package/zoho/package.json +8 -8
package/index.cjs.js
CHANGED
|
@@ -7,6 +7,7 @@ var date = require('@dereekb/date');
|
|
|
7
7
|
var makeError = require('make-error');
|
|
8
8
|
var rxjs = require('rxjs');
|
|
9
9
|
var firestore = require('@google-cloud/firestore');
|
|
10
|
+
var crypto = require('crypto');
|
|
10
11
|
var common = require('@nestjs/common');
|
|
11
12
|
var nestjs = require('@dereekb/nestjs');
|
|
12
13
|
var v2 = require('firebase-functions/v2');
|
|
@@ -1048,6 +1049,140 @@ function googleCloudFirestoreDrivers() {
|
|
|
1048
1049
|
*/
|
|
1049
1050
|
const googleCloudFirestoreContextFactory = firebase.firestoreContextFactory(googleCloudFirestoreDrivers());
|
|
1050
1051
|
|
|
1052
|
+
// MARK: Encrypted Field
|
|
1053
|
+
/**
|
|
1054
|
+
* AES-256-GCM encryption constants.
|
|
1055
|
+
*/
|
|
1056
|
+
const ENCRYPTED_FIELD_ALGORITHM = 'aes-256-gcm';
|
|
1057
|
+
const ENCRYPTED_FIELD_IV_LENGTH = 12;
|
|
1058
|
+
const ENCRYPTED_FIELD_AUTH_TAG_LENGTH = 16;
|
|
1059
|
+
const ENCRYPTED_FIELD_KEY_LENGTH = 32;
|
|
1060
|
+
/**
|
|
1061
|
+
* Resolves the encryption key Buffer from a secret source.
|
|
1062
|
+
*
|
|
1063
|
+
* @param source - The secret source configuration.
|
|
1064
|
+
* @returns A 32-byte Buffer for AES-256 encryption.
|
|
1065
|
+
* @throws Error if the resolved key is not 64 hex characters.
|
|
1066
|
+
*/
|
|
1067
|
+
function resolveEncryptionKey(source) {
|
|
1068
|
+
let hex;
|
|
1069
|
+
if (typeof source === 'string') {
|
|
1070
|
+
hex = source;
|
|
1071
|
+
}
|
|
1072
|
+
else if (typeof source === 'function') {
|
|
1073
|
+
hex = source();
|
|
1074
|
+
}
|
|
1075
|
+
else {
|
|
1076
|
+
const envValue = process.env[source.env];
|
|
1077
|
+
if (!envValue) {
|
|
1078
|
+
throw new Error(`firestoreEncryptedField: environment variable "${source.env}" is not set.`);
|
|
1079
|
+
}
|
|
1080
|
+
hex = envValue;
|
|
1081
|
+
}
|
|
1082
|
+
if (hex.length !== ENCRYPTED_FIELD_KEY_LENGTH * 2) {
|
|
1083
|
+
throw new Error(`firestoreEncryptedField: expected a ${ENCRYPTED_FIELD_KEY_LENGTH * 2}-character hex key, got ${hex.length} characters.`);
|
|
1084
|
+
}
|
|
1085
|
+
return Buffer.from(hex, 'hex');
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Encrypts a JSON-serializable value to a base64-encoded string.
|
|
1089
|
+
*
|
|
1090
|
+
* Format: base64(IV (12 bytes) + ciphertext + authTag (16 bytes))
|
|
1091
|
+
*
|
|
1092
|
+
* @param value - The value to encrypt.
|
|
1093
|
+
* @param key - The 32-byte encryption key.
|
|
1094
|
+
* @returns The encrypted value as a base64 string.
|
|
1095
|
+
*/
|
|
1096
|
+
function encryptValue(value, key) {
|
|
1097
|
+
const iv = crypto.randomBytes(ENCRYPTED_FIELD_IV_LENGTH);
|
|
1098
|
+
const cipher = crypto.createCipheriv(ENCRYPTED_FIELD_ALGORITHM, key, iv);
|
|
1099
|
+
const plaintext = JSON.stringify(value);
|
|
1100
|
+
const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
|
|
1101
|
+
const authTag = cipher.getAuthTag();
|
|
1102
|
+
const combined = Buffer.concat([iv, encrypted, authTag]);
|
|
1103
|
+
return combined.toString('base64');
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
1106
|
+
* Decrypts a base64-encoded string back to the original value.
|
|
1107
|
+
*
|
|
1108
|
+
* @param encoded - The base64-encoded encrypted string (IV + ciphertext + authTag).
|
|
1109
|
+
* @param key - The 32-byte encryption key.
|
|
1110
|
+
* @returns The decrypted value.
|
|
1111
|
+
*/
|
|
1112
|
+
function decryptValue(encoded, key) {
|
|
1113
|
+
const combined = Buffer.from(encoded, 'base64');
|
|
1114
|
+
const iv = combined.subarray(0, ENCRYPTED_FIELD_IV_LENGTH);
|
|
1115
|
+
const authTag = combined.subarray(combined.length - ENCRYPTED_FIELD_AUTH_TAG_LENGTH);
|
|
1116
|
+
const ciphertext = combined.subarray(ENCRYPTED_FIELD_IV_LENGTH, combined.length - ENCRYPTED_FIELD_AUTH_TAG_LENGTH);
|
|
1117
|
+
const decipher = crypto.createDecipheriv(ENCRYPTED_FIELD_ALGORITHM, key, iv);
|
|
1118
|
+
decipher.setAuthTag(authTag);
|
|
1119
|
+
const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
1120
|
+
return JSON.parse(decrypted.toString('utf8'));
|
|
1121
|
+
}
|
|
1122
|
+
/**
|
|
1123
|
+
* Creates a Firestore field mapping that encrypts/decrypts a JSON-serializable value
|
|
1124
|
+
* using AES-256-GCM. The value is stored in Firestore as a base64-encoded string.
|
|
1125
|
+
*
|
|
1126
|
+
* The encryption key is resolved from the configured secret source on each read/write,
|
|
1127
|
+
* allowing for key rotation via environment variable changes.
|
|
1128
|
+
*
|
|
1129
|
+
* @example
|
|
1130
|
+
* ```typescript
|
|
1131
|
+
* const jwksField = firestoreEncryptedField<JWKSet>({
|
|
1132
|
+
* secret: { env: 'FIRESTORE_ENCRYPTION_KEY' },
|
|
1133
|
+
* default: () => ({ keys: [] })
|
|
1134
|
+
* });
|
|
1135
|
+
* ```
|
|
1136
|
+
*
|
|
1137
|
+
* @template T - The JSON-serializable value type.
|
|
1138
|
+
* @param config - Encryption field configuration.
|
|
1139
|
+
* @returns A field mapping configuration for encrypted values.
|
|
1140
|
+
*/
|
|
1141
|
+
function firestoreEncryptedField(config) {
|
|
1142
|
+
const { secret, default: defaultValue } = config;
|
|
1143
|
+
return firebase.firestoreField({
|
|
1144
|
+
default: defaultValue,
|
|
1145
|
+
fromData: (data) => {
|
|
1146
|
+
const key = resolveEncryptionKey(secret);
|
|
1147
|
+
return decryptValue(data, key);
|
|
1148
|
+
},
|
|
1149
|
+
toData: (value) => {
|
|
1150
|
+
const key = resolveEncryptionKey(secret);
|
|
1151
|
+
return encryptValue(value, key);
|
|
1152
|
+
}
|
|
1153
|
+
});
|
|
1154
|
+
}
|
|
1155
|
+
/**
|
|
1156
|
+
* Creates a Firestore field mapping for an optional encrypted field.
|
|
1157
|
+
*
|
|
1158
|
+
* When the value is null/undefined, it is stored/read as null. When present, it is
|
|
1159
|
+
* encrypted/decrypted using AES-256-GCM.
|
|
1160
|
+
*
|
|
1161
|
+
* @example
|
|
1162
|
+
* ```typescript
|
|
1163
|
+
* const optionalSecretField = optionalFirestoreEncryptedField<OAuthClientSecret>({
|
|
1164
|
+
* secret: { env: 'FIRESTORE_ENCRYPTION_KEY' }
|
|
1165
|
+
* });
|
|
1166
|
+
* ```
|
|
1167
|
+
*
|
|
1168
|
+
* @template T - The JSON-serializable value type.
|
|
1169
|
+
* @param config - Encryption field configuration.
|
|
1170
|
+
* @returns A field mapping configuration for optional encrypted values.
|
|
1171
|
+
*/
|
|
1172
|
+
function optionalFirestoreEncryptedField(config) {
|
|
1173
|
+
const { secret } = config;
|
|
1174
|
+
return firebase.optionalFirestoreField({
|
|
1175
|
+
transformFromData: (data) => {
|
|
1176
|
+
const key = resolveEncryptionKey(secret);
|
|
1177
|
+
return decryptValue(data, key);
|
|
1178
|
+
},
|
|
1179
|
+
transformToData: (value) => {
|
|
1180
|
+
const key = resolveEncryptionKey(secret);
|
|
1181
|
+
return encryptValue(value, key);
|
|
1182
|
+
}
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1051
1186
|
function assertContextHasAuth(context) {
|
|
1052
1187
|
if (!isContextWithAuthData(context)) {
|
|
1053
1188
|
throw unauthenticatedContextHasNoUidError();
|
|
@@ -2766,6 +2901,7 @@ exports.firebaseServerStorageModuleMetadata = firebaseServerStorageModuleMetadat
|
|
|
2766
2901
|
exports.firebaseServerValidationError = firebaseServerValidationError;
|
|
2767
2902
|
exports.firebaseServerValidationServerError = firebaseServerValidationServerError;
|
|
2768
2903
|
exports.firestoreClientQueryConstraintFunctionsDriver = firestoreClientQueryConstraintFunctionsDriver;
|
|
2904
|
+
exports.firestoreEncryptedField = firestoreEncryptedField;
|
|
2769
2905
|
exports.firestoreServerIncrementUpdateToUpdateData = firestoreServerIncrementUpdateToUpdateData;
|
|
2770
2906
|
exports.forbiddenError = forbiddenError;
|
|
2771
2907
|
exports.getAuthUserOrUndefined = getAuthUserOrUndefined;
|
|
@@ -2822,6 +2958,7 @@ exports.onCallUpdateModel = onCallUpdateModel;
|
|
|
2822
2958
|
exports.onScheduleHandlerWithNestApplicationFactory = onScheduleHandlerWithNestApplicationFactory;
|
|
2823
2959
|
exports.onScheduleHandlerWithNestContextFactory = onScheduleHandlerWithNestContextFactory;
|
|
2824
2960
|
exports.optionalAuthContext = optionalAuthContext;
|
|
2961
|
+
exports.optionalFirestoreEncryptedField = optionalFirestoreEncryptedField;
|
|
2825
2962
|
exports.permissionDeniedError = permissionDeniedError;
|
|
2826
2963
|
exports.phoneNumberAlreadyExistsError = phoneNumberAlreadyExistsError;
|
|
2827
2964
|
exports.preconditionConflictError = preconditionConflictError;
|