secure-store-redis
Advanced tools
Comparing version 2.0.5 to 3.0.0-rc.1
@@ -1,5 +0,4 @@ | ||
import { RedisConnectionPoolConfig } from "redis-connection-pool"; | ||
import { RedisClientOptions } from "redis"; | ||
interface SecureStoreConfig { | ||
redis?: any; | ||
redisConnectionPool?: RedisConnectionPoolConfig; | ||
redis: RedisClientOptions; | ||
} | ||
@@ -9,5 +8,5 @@ export default class SecureStore { | ||
secret: string; | ||
private pool; | ||
private client; | ||
private config; | ||
constructor(uid: string, secret: string, cfg?: SecureStoreConfig); | ||
constructor(uid: string, secret: string, cfg: SecureStoreConfig); | ||
init(): Promise<void>; | ||
@@ -14,0 +13,0 @@ save(key: string, data: any, postfix?: string): Promise<number>; |
@@ -15,41 +15,49 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const redis_connection_pool_1 = __importDefault(require("redis-connection-pool")); | ||
const crypto_1 = require("crypto"); | ||
const ALGORITHM = 'aes-256-cbc', IV_LENGTH = 16; | ||
const redis_1 = require("redis"); | ||
const debug_1 = __importDefault(require("debug")); | ||
const log = (0, debug_1.default)("secure-store-redis"); | ||
const ALGORITHM = "aes-256-cbc", IV_LENGTH = 16; | ||
class SecureStore { | ||
constructor(uid, secret, cfg = {}) { | ||
if (typeof uid !== 'string') { | ||
throw new Error('A uid must be specified'); | ||
constructor(uid, secret, cfg) { | ||
if (typeof uid !== "string") { | ||
throw new Error("A uid must be specified"); | ||
} | ||
else if (typeof secret !== 'string') { | ||
throw new Error('No secret specified'); | ||
else if (typeof secret !== "string") { | ||
throw new Error("No secret specified"); | ||
} | ||
else if (secret.length !== 32) { | ||
throw new Error('Secret must be 32 char string'); | ||
throw new Error("Secret must be 32 char string"); | ||
} | ||
this.uid = uid; | ||
this.secret = secret; | ||
const redis = cfg.redis || {}; | ||
const redisConnectionPoolConfig = cfg.redisConnectionPool || {}; | ||
if (redis) { | ||
redisConnectionPoolConfig.redis = redis; | ||
} | ||
this.config = redisConnectionPoolConfig; | ||
this.config = cfg; | ||
} | ||
init() { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
this.pool = yield (0, redis_connection_pool_1.default)(this.uid, this.config); | ||
yield this.pool.init(); | ||
return new Promise((resolve, reject) => { | ||
const client = (0, redis_1.createClient)(this.config.redis); | ||
client.on("error", (err) => { | ||
log("error connecting", err); | ||
return reject(err); | ||
}); | ||
client.connect().then(() => { | ||
log("connected"); | ||
this.client = client; | ||
resolve(); | ||
}); | ||
}); | ||
}); | ||
} | ||
save(key, data, postfix = '') { | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
save(key, data, postfix = "") { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
if (typeof key !== 'string') { | ||
throw new Error('No hash key specified'); | ||
if (typeof key !== "string") { | ||
throw new Error("No hash key specified"); | ||
} | ||
else if (!data) { | ||
throw new Error('No data provided, nothing to save'); | ||
throw new Error("No data provided, nothing to save"); | ||
} | ||
postfix = postfix ? ':' + postfix : ''; | ||
if (typeof data === 'object') { | ||
postfix = postfix ? ":" + postfix : ""; | ||
if (typeof data === "object") { | ||
try { | ||
@@ -64,15 +72,15 @@ data = JSON.stringify(data); | ||
const hash = SecureStore.shasum(key); | ||
return yield this.pool.hset(this.uid + postfix, hash, data); | ||
return yield this.client.hSet(this.uid + postfix, hash, data); | ||
}); | ||
} | ||
get(key, postfix = '') { | ||
get(key, postfix = "") { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
if (typeof key !== 'string') { | ||
throw new Error('No hash key specified'); | ||
if (typeof key !== "string") { | ||
throw new Error("No hash key specified"); | ||
} | ||
postfix = postfix ? ':' + postfix : ''; | ||
postfix = postfix ? ":" + postfix : ""; | ||
const hash = SecureStore.shasum(key); | ||
const res = yield this.pool.hget(this.uid + postfix, hash); | ||
const res = yield this.client.hGet(this.uid + postfix, hash); | ||
let data; | ||
if (typeof res === 'string') { | ||
if (typeof res === "string") { | ||
try { | ||
@@ -86,2 +94,3 @@ data = this.decrypt(res); | ||
data = JSON.parse(data); | ||
// eslint-disable-next-line no-empty | ||
} | ||
@@ -96,14 +105,13 @@ catch (e) { } | ||
} | ||
delete(key, postfix = '') { | ||
delete(key, postfix = "") { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
if (typeof key !== 'string') { | ||
throw new Error('No hash key specified'); | ||
if (typeof key !== "string") { | ||
throw new Error("No hash key specified"); | ||
} | ||
postfix = postfix ? ':' + postfix : ''; | ||
postfix = postfix ? ":" + postfix : ""; | ||
const hash = SecureStore.shasum(key); | ||
// @ts-ignore | ||
return yield this.pool.hdel(this.uid + postfix, hash); | ||
return yield this.client.hDel(this.uid + postfix, hash); | ||
}); | ||
} | ||
; | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
encrypt(data) { | ||
@@ -114,8 +122,8 @@ const iv = (0, crypto_1.randomBytes)(IV_LENGTH); | ||
encrypted = Buffer.concat([encrypted, cipher.final()]); | ||
return iv.toString('hex') + ':' + encrypted.toString('hex'); | ||
return iv.toString("hex") + ":" + encrypted.toString("hex"); | ||
} | ||
decrypt(encrypted) { | ||
let parts = encrypted.split(':'); | ||
const iv = Buffer.from(parts.shift(), 'hex'); | ||
const encryptedText = Buffer.from(parts.join(':'), 'hex'); | ||
const parts = encrypted.split(":"); | ||
const iv = Buffer.from(parts.shift(), "hex"); | ||
const encryptedText = Buffer.from(parts.join(":"), "hex"); | ||
const decipher = (0, crypto_1.createDecipheriv)(ALGORITHM, Buffer.from(this.secret), iv); | ||
@@ -127,5 +135,5 @@ let decrypted = decipher.update(encryptedText); | ||
static shasum(text) { | ||
const s = (0, crypto_1.createHash)('sha256'); | ||
const s = (0, crypto_1.createHash)("sha256"); | ||
s.update(text); | ||
return s.digest('hex'); | ||
return s.digest("hex"); | ||
} | ||
@@ -132,0 +140,0 @@ } |
{ | ||
"name": "secure-store-redis", | ||
"version": "2.0.5", | ||
"version": "3.0.0-rc.1", | ||
"description": "A simple wrapper to encrypt and decrypt data stored in Redis", | ||
"license": "MIT", | ||
"private": false, | ||
"packageManager": "pnpm@8.0.0", | ||
"engines": { | ||
"node": ">=16", | ||
"pnpm": ">=8" | ||
}, | ||
"keywords": [ | ||
@@ -26,21 +31,21 @@ "redis", | ||
], | ||
"scripts": { | ||
"test": "npm run build && jaribu", | ||
"lint": "eslint src/", | ||
"lint:fix": "eslint --fix src/", | ||
"build": "tsc" | ||
}, | ||
"dependencies": { | ||
"redis-connection-pool": "^4.0.1" | ||
"redis": "4.6.6" | ||
}, | ||
"devDependencies": { | ||
"@types/chai": "4.3.5", | ||
"@types/debug": "4.1.7", | ||
"@types/eslint": "8.4.6", | ||
"@types/node": "^18.7.13", | ||
"@typescript-eslint/eslint-plugin": "^5.35.1", | ||
"@typescript-eslint/parser": "^5.35.1", | ||
"@types/mocha": "10.0.1", | ||
"@types/node": "18.7.13", | ||
"@typescript-eslint/eslint-plugin": "5.36.0", | ||
"@typescript-eslint/parser": "5.36.0", | ||
"chai": "4.3.7", | ||
"debug": "4.3.4", | ||
"eslint": "8.22.0", | ||
"eslint-plugin-security-node": "1.1.1", | ||
"jaribu": "2.2.3", | ||
"typescript": "^4.8.2", | ||
"typescript-eslint": "0.0.1-alpha.0" | ||
"mocha": "10.2.0", | ||
"prettier": "2.8.8", | ||
"ts-node": "10.9.1", | ||
"typescript": "4.8.2" | ||
}, | ||
@@ -55,3 +60,10 @@ "repository": { | ||
}, | ||
"homepage": "https://github.com/silverbucket/secure-store-redis" | ||
} | ||
"homepage": "https://github.com/silverbucket/secure-store-redis", | ||
"scripts": { | ||
"preinstall": "npx only-allow pnpm", | ||
"test": "mocha -r ts-node/register src/*.test.ts", | ||
"lint": "prettier --check . && eslint --max-warnings 0 .", | ||
"lint:fix": "prettier --write . && eslint --max-warnings 0 --fix .", | ||
"build": "tsc" | ||
} | ||
} |
# secure-store-redis | ||
A simple wrapper to encrypt and decrypt data stored in redis. | ||
The main point is to ensure that any data you store in redis cannot be accessed | ||
A simple wrapper to encrypt and decrypt data stored in redis. | ||
The main point is to ensure that any data you store in redis cannot be accessed | ||
by anyone else, without the key. | ||
**NOTE** version `2.x` is a rewrite in TypeScript, using async functions, and is | ||
**NOTE** version `2.x` is a rewrite in TypeScript, using async functions, and is | ||
backwards incompatible with `1.x` | ||
```javascript | ||
const SecureStore = require('secure-store-redis').default; | ||
const SecureStore = require("secure-store-redis").default; | ||
const store = new SecureStore('myApp:store', '823HD8DG26JA0LK1239Hgb651TWfs0j1', { | ||
redis: { | ||
host: 'localhost', | ||
port: 6379, // optional | ||
// optionally use the 'url' property to specify entire redis connect string | ||
// url: 'redis://localhost:6379', | ||
} // optional | ||
}); | ||
const store = new SecureStore( | ||
"myApp:store", | ||
"823HD8DG26JA0LK1239Hgb651TWfs0j1", | ||
{ | ||
redis: { | ||
host: "localhost", | ||
port: 6379, // optional | ||
// optionally use the 'url' property to specify entire redis connect string | ||
// url: 'redis://localhost:6379', | ||
}, // optional | ||
} | ||
); | ||
await store.init(); | ||
await store.save('quote', 'i see dead people'); | ||
let res = await store.get('quote'); | ||
await store.save("quote", "i see dead people"); | ||
let res = await store.get("quote"); | ||
// res: 'i see dead people' | ||
let res = await store.get('quote'); | ||
let res = await store.get("quote"); | ||
// res: null | ||
const num = await store.delete('quote'); | ||
const num = await store.delete("quote"); | ||
// num: 1 | ||
await store.save('quote', 'i see dead people again'); | ||
await store.save("quote", "i see dead people again"); | ||
const otherStore = new SecureStore('myApp:store', 'this is the wrong secret', { | ||
host: "127.0.0.1", | ||
port: 6379 | ||
const otherStore = new SecureStore("myApp:store", "this is the wrong secret", { | ||
host: "127.0.0.1", | ||
port: 6379, | ||
}); | ||
await otherStore.init(); | ||
let res = await otherStore.get('quote'); | ||
let res = await otherStore.get("quote"); | ||
// res: undefined | ||
``` |
130
src/index.ts
@@ -1,12 +0,25 @@ | ||
import redisConnectionPoolFactory, { | ||
RedisConnectionPool, RedisConnectionPoolConfig | ||
} from "redis-connection-pool"; | ||
import {randomBytes, createCipheriv, createDecipheriv, createHash} from 'crypto'; | ||
import { | ||
randomBytes, | ||
createCipheriv, | ||
createDecipheriv, | ||
createHash, | ||
} from "crypto"; | ||
import { | ||
createClient, | ||
RedisClientOptions, | ||
RedisClientType, | ||
RedisFunctions, | ||
RedisModules, | ||
RedisScripts, | ||
} from "redis"; | ||
const ALGORITHM = 'aes-256-cbc', | ||
IV_LENGTH = 16; | ||
import debug from "debug"; | ||
const log = debug("secure-store-redis"); | ||
const ALGORITHM = "aes-256-cbc", | ||
IV_LENGTH = 16; | ||
interface SecureStoreConfig { | ||
redis?: any; | ||
redisConnectionPool?: RedisConnectionPoolConfig; | ||
redis: RedisClientOptions; | ||
} | ||
@@ -17,37 +30,51 @@ | ||
secret: string; | ||
private pool: RedisConnectionPool; | ||
private config: object; | ||
client: RedisClientType<RedisModules, RedisFunctions, RedisScripts>; | ||
private config: SecureStoreConfig; | ||
constructor(uid: string, secret: string, cfg: SecureStoreConfig = {}) { | ||
if (typeof uid !== 'string') { | ||
throw new Error('A uid must be specified'); | ||
} else if (typeof secret !== 'string') { | ||
throw new Error('No secret specified'); | ||
constructor(uid: string, secret: string, cfg: SecureStoreConfig) { | ||
if (typeof uid !== "string") { | ||
throw new Error("A uid must be specified"); | ||
} else if (typeof secret !== "string") { | ||
throw new Error("No secret specified"); | ||
} else if (secret.length !== 32) { | ||
throw new Error('Secret must be 32 char string'); | ||
throw new Error("Secret must be 32 char string"); | ||
} | ||
this.uid = uid; | ||
this.secret = secret; | ||
const redis = cfg.redis || {}; | ||
const redisConnectionPoolConfig = cfg.redisConnectionPool || {}; | ||
if (redis) { | ||
redisConnectionPoolConfig.redis = redis; | ||
} | ||
this.config = redisConnectionPoolConfig; | ||
this.config = cfg; | ||
} | ||
async init() { | ||
this.pool = await redisConnectionPoolFactory(this.uid, this.config); | ||
await this.pool.init(); | ||
async quit() { | ||
return this.client.quit(); | ||
} | ||
async save(key: string, data: any, postfix: string = '') { | ||
if (typeof key !== 'string') { | ||
throw new Error('No hash key specified'); | ||
async disconnect() { | ||
return this.client.disconnect(); | ||
} | ||
async init(): Promise<void> { | ||
return new Promise((resolve, reject) => { | ||
const client = createClient(this.config.redis); | ||
client.on("error", (err) => { | ||
log("error connecting", err); | ||
return reject(err); | ||
}); | ||
client.connect().then(() => { | ||
log("connected"); | ||
this.client = client; | ||
resolve(); | ||
}); | ||
}); | ||
} | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
async save(key: string, data: any, postfix = "") { | ||
if (typeof key !== "string") { | ||
throw new Error("No hash key specified"); | ||
} else if (!data) { | ||
throw new Error('No data provided, nothing to save'); | ||
throw new Error("No data provided, nothing to save"); | ||
} | ||
postfix = postfix ? ':' + postfix : ''; | ||
postfix = postfix ? ":" + postfix : ""; | ||
if (typeof data === 'object') { | ||
if (typeof data === "object") { | ||
try { | ||
@@ -62,15 +89,15 @@ data = JSON.stringify(data); | ||
const hash = SecureStore.shasum(key); | ||
return await this.pool.hset(this.uid + postfix, hash, data); | ||
return await this.client.HSET(this.uid + postfix, hash, data); | ||
} | ||
async get(key: string, postfix: string = '') { | ||
if (typeof key !== 'string') { | ||
throw new Error('No hash key specified'); | ||
async get(key: string, postfix = "") { | ||
if (typeof key !== "string") { | ||
throw new Error("No hash key specified"); | ||
} | ||
postfix = postfix ? ':' + postfix : ''; | ||
postfix = postfix ? ":" + postfix : ""; | ||
const hash = SecureStore.shasum(key); | ||
const res = await this.pool.hget(this.uid + postfix, hash); | ||
const res = await this.client.HGET(this.uid + postfix, hash); | ||
let data; | ||
if (typeof res === 'string') { | ||
if (typeof res === "string") { | ||
try { | ||
@@ -84,2 +111,3 @@ data = this.decrypt(res); | ||
data = JSON.parse(data); | ||
// eslint-disable-next-line no-empty | ||
} catch (e) {} | ||
@@ -92,12 +120,12 @@ } else { | ||
async delete(key: string, postfix = '') { | ||
if (typeof key !== 'string') { | ||
throw new Error('No hash key specified'); | ||
async delete(key: string, postfix = "") { | ||
if (typeof key !== "string") { | ||
throw new Error("No hash key specified"); | ||
} | ||
postfix = postfix ? ':' + postfix : ''; | ||
postfix = postfix ? ":" + postfix : ""; | ||
const hash = SecureStore.shasum(key); | ||
// @ts-ignore | ||
return await this.pool.hdel(this.uid + postfix, hash); | ||
}; | ||
return await this.client.HDEL(this.uid + postfix, hash); | ||
} | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
private encrypt(data: any): string { | ||
@@ -109,9 +137,9 @@ const iv = randomBytes(IV_LENGTH); | ||
encrypted = Buffer.concat([encrypted, cipher.final()]); | ||
return iv.toString('hex') + ':' + encrypted.toString('hex'); | ||
return iv.toString("hex") + ":" + encrypted.toString("hex"); | ||
} | ||
private decrypt(encrypted: string): string { | ||
let parts = encrypted.split(':'); | ||
const iv = Buffer.from(parts.shift(), 'hex'); | ||
const encryptedText = Buffer.from(parts.join(':'), 'hex'); | ||
const parts = encrypted.split(":"); | ||
const iv = Buffer.from(parts.shift(), "hex"); | ||
const encryptedText = Buffer.from(parts.join(":"), "hex"); | ||
const decipher = createDecipheriv(ALGORITHM, Buffer.from(this.secret), iv); | ||
@@ -125,6 +153,6 @@ let decrypted = decipher.update(encryptedText); | ||
private static shasum(text: string): string { | ||
const s = createHash('sha256'); | ||
const s = createHash("sha256"); | ||
s.update(text); | ||
return s.digest('hex'); | ||
return s.digest("hex"); | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Install scripts
Supply chain riskInstall scripts are run when the package is installed. The majority of malware in npm is hidden in install scripts.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
21116
8
400
46
15
1
1
1
+ Addedredis@4.6.6
+ Added@redis/client@1.5.7(transitive)
+ Added@redis/graph@1.1.0(transitive)
+ Added@redis/json@1.0.4(transitive)
+ Added@redis/search@1.1.2(transitive)
+ Added@redis/time-series@1.0.4(transitive)
+ Addedredis@4.6.6(transitive)
- Removedredis-connection-pool@^4.0.1
- Removed@redis/client@1.6.0(transitive)
- Removed@redis/graph@1.1.1(transitive)
- Removed@redis/json@1.0.7(transitive)
- Removed@redis/search@1.2.0(transitive)
- Removed@redis/time-series@1.1.0(transitive)
- Removeddebug@4.3.7(transitive)
- Removedms@2.1.3(transitive)
- Removedredis@4.7.0(transitive)
- Removedredis-connection-pool@4.0.1(transitive)