Skip to content

Commit 920d38f

Browse files
authored
Accept keys as a Secret object (#31)
* feat: add support for passing keys as a Secret object * refactor: remove need of retyping encryption factory function arguments
1 parent 7693885 commit 920d38f

File tree

9 files changed

+76
-52
lines changed

9 files changed

+76
-52
lines changed

src/drivers/aes_256_cbc.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,28 @@
66
*/
77

88
import { createCipheriv, createDecipheriv, hkdfSync, randomBytes } from 'node:crypto'
9-
import { MessageBuilder } from '@poppinss/utils'
9+
import { MessageBuilder, type Secret } from '@poppinss/utils'
1010
import { BaseDriver } from './base_driver.ts'
1111
import { Hmac } from '../hmac.ts'
1212
import * as errors from '../exceptions.ts'
13-
import type { AES256CBCConfig, CypherText, EncryptionDriverContract } from '../types/main.ts'
13+
import type {
14+
AES256CBCConfig,
15+
CypherText,
16+
EncryptionConfig,
17+
EncryptionDriverContract,
18+
} from '../types/main.ts'
1419
import { base64UrlDecode, base64UrlEncode } from '../base64.ts'
1520

1621
export interface AES256CBCDriverConfig {
1722
id: string
18-
keys: string[]
23+
keys: (string | Secret<string>)[]
1924
}
2025

2126
export function aes256cbc(config: AES256CBCDriverConfig) {
2227
return {
23-
driver: (key: string) => new AES256CBC({ id: config.id, key }),
28+
driver: (key) => new AES256CBC({ id: config.id, key }),
2429
keys: config.keys,
25-
}
30+
} satisfies EncryptionConfig
2631
}
2732

2833
export class AES256CBC extends BaseDriver implements EncryptionDriverContract {

src/drivers/aes_256_gcm.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,27 @@
66
*/
77

88
import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto'
9-
import { MessageBuilder } from '@poppinss/utils'
9+
import { MessageBuilder, type Secret } from '@poppinss/utils'
1010
import { BaseDriver } from './base_driver.ts'
1111
import * as errors from '../exceptions.ts'
1212
import { base64UrlDecode, base64UrlEncode } from '../base64.ts'
13-
import type { AES256GCMConfig, CypherText, EncryptionDriverContract } from '../types/main.ts'
13+
import type {
14+
AES256GCMConfig,
15+
CypherText,
16+
EncryptionConfig,
17+
EncryptionDriverContract,
18+
} from '../types/main.ts'
1419

1520
export interface AES256GCMDriverConfig {
1621
id: string
17-
keys: string[]
22+
keys: (string | Secret<string>)[]
1823
}
1924

2025
export function aes256gcm(config: AES256GCMDriverConfig) {
2126
return {
22-
driver: (key: string) => new AES256GCM({ id: config.id, key }),
27+
driver: (key) => new AES256GCM({ id: config.id, key }),
2328
keys: config.keys,
24-
}
29+
} satisfies EncryptionConfig
2530
}
2631

2732
export class AES256GCM extends BaseDriver implements EncryptionDriverContract {

src/drivers/base_driver.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import { createHash } from 'node:crypto'
99
import * as errors from '../exceptions.ts'
1010
import type { BaseConfig, CypherText } from '../types/main.ts'
11+
import { type Secret } from '@poppinss/utils'
1112

1213
export abstract class BaseDriver {
1314
/**
@@ -23,21 +24,24 @@ export abstract class BaseDriver {
2324
separator = '.'
2425

2526
protected constructor(config: BaseConfig) {
26-
this.#validateSecret(config.key)
27-
this.cryptoKey = createHash('sha256').update(config.key).digest()
27+
const key = this.#validateAndGetSecret(config.key)
28+
this.cryptoKey = createHash('sha256').update(key).digest()
2829
}
2930

3031
/**
31-
* Validates the app secret
32+
* Validates the app secret and returns it back as a string
3233
*/
33-
#validateSecret(secret: string) {
34+
#validateAndGetSecret(secret: string | Secret<string>): string {
3435
if (!secret) {
3536
throw new errors.E_MISSING_ENCRYPTER_KEY()
3637
}
3738

38-
if (secret.length < 16) {
39+
const revealedSecret = typeof secret === 'string' ? secret : secret.release()
40+
if (revealedSecret.length < 16) {
3941
throw new errors.E_INSECURE_ENCRYPTER_KEY()
4042
}
43+
44+
return revealedSecret
4145
}
4246

4347
protected computeReturns(values: string[]) {

src/drivers/chacha20_poly1305.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,27 @@
66
*/
77

88
import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto'
9-
import { MessageBuilder } from '@poppinss/utils'
9+
import { MessageBuilder, type Secret } from '@poppinss/utils'
1010
import { BaseDriver } from './base_driver.ts'
1111
import * as errors from '../exceptions.ts'
12-
import type { ChaCha20Poly1305Config, CypherText, EncryptionDriverContract } from '../types/main.ts'
12+
import type {
13+
ChaCha20Poly1305Config,
14+
CypherText,
15+
EncryptionConfig,
16+
EncryptionDriverContract,
17+
} from '../types/main.ts'
1318
import { base64UrlDecode, base64UrlEncode } from '../base64.ts'
1419

1520
export interface ChaCha20Poly1305DriverConfig {
1621
id: string
17-
keys: string[]
22+
keys: (string | Secret<string>)[]
1823
}
1924

2025
export function chacha20poly1305(config: ChaCha20Poly1305DriverConfig) {
2126
return {
22-
driver: (key: string) => new ChaCha20Poly1305({ id: config.id, key }),
27+
driver: (key) => new ChaCha20Poly1305({ id: config.id, key }),
2328
keys: config.keys,
24-
}
29+
} satisfies EncryptionConfig
2530
}
2631

2732
export class ChaCha20Poly1305 extends BaseDriver implements EncryptionDriverContract {

src/encryption.ts

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,7 @@
66
*/
77

88
import { MessageVerifier } from './message_verifier.ts'
9-
import type { CypherText, EncryptionDriverContract } from './types/main.ts'
10-
11-
/**
12-
* Configuration for the Encryption class
13-
*/
14-
export interface EncryptionConfig {
15-
/**
16-
* Factory function that creates a driver instance for a given key
17-
*/
18-
driver: (key: string) => EncryptionDriverContract
19-
20-
/**
21-
* List of keys to use for encryption/decryption.
22-
* The first key is used for encryption, all keys are tried for decryption.
23-
*/
24-
keys: string[]
25-
}
9+
import type { CypherText, EncryptionConfig, EncryptionDriverContract } from './types/main.ts'
2610

2711
/**
2812
* Encryption class that wraps a driver and manages multiple keys.

src/encryption_manager.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77

88
import { RuntimeException } from '@poppinss/utils/exception'
99
import debug from './debug.ts'
10-
import { Encryption, type EncryptionConfig } from './encryption.ts'
10+
import { Encryption } from './encryption.ts'
1111
import type { MessageVerifier } from './message_verifier.ts'
12-
import type { CypherText } from './types/main.ts'
12+
import type { CypherText, EncryptionConfig } from './types/main.ts'
1313

1414
export class EncryptionManager<KnownEncrypters extends Record<string, EncryptionConfig>> {
1515
/**

src/message_verifier.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
*/
77

88
import { createHash } from 'node:crypto'
9-
import { MessageBuilder } from '@poppinss/utils'
9+
import { MessageBuilder, type Secret } from '@poppinss/utils'
1010
import { RuntimeException } from '@poppinss/utils/exception'
1111
import { base64UrlEncode, base64UrlDecode } from './base64.ts'
1212
import { Hmac } from './hmac.ts'
@@ -31,9 +31,12 @@ export class MessageVerifier {
3131
*/
3232
#separator = '.'
3333

34-
constructor(secret: string | string[]) {
35-
const secrets = Array.isArray(secret) ? secret : [secret]
36-
this.#cryptoKeys = secrets.map((s) => createHash('sha256').update(s).digest())
34+
constructor(secrets: (string | Secret<string>)[]) {
35+
this.#cryptoKeys = secrets.map((s) =>
36+
createHash('sha256')
37+
.update(typeof s === 'string' ? s : s.release())
38+
.digest()
39+
)
3740
}
3841

3942
/**

src/types/main.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
* @copyright Boring Node
66
*/
77

8+
import { type Secret } from '@poppinss/utils'
9+
810
export type CypherText = `${string}.${string}.${string}.${string}`
911

1012
/**
@@ -41,7 +43,7 @@ export interface EncryptionDriverContract {
4143
export type ManagerDriverFactory = () => EncryptionDriverContract
4244

4345
export interface BaseConfig {
44-
key: string
46+
key: string | Secret<string>
4547
}
4648

4749
export interface LegacyConfig extends BaseConfig {}
@@ -59,3 +61,19 @@ export type Config<KnownEncrypters extends Record<string, ManagerDriverFactory>>
5961
default?: keyof KnownEncrypters
6062
list: KnownEncrypters
6163
}
64+
65+
/**
66+
* Configuration for the Encryption class
67+
*/
68+
export interface EncryptionConfig {
69+
/**
70+
* Factory function that creates a driver instance for a given key
71+
*/
72+
driver: (key: string | Secret<string>) => EncryptionDriverContract
73+
74+
/**
75+
* List of keys to use for encryption/decryption.
76+
* The first key is used for encryption, all keys are tried for decryption.
77+
*/
78+
keys: (string | Secret<string>)[]
79+
}

tests/message_verifier.spec.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,21 @@ const SECRET = 'averylongradom32charactersstring'
1313

1414
test.group('MessageVerifier', () => {
1515
test('disallow signing null and undefined values', ({ assert }) => {
16-
const messageVerifier = new MessageVerifier(SECRET)
16+
const messageVerifier = new MessageVerifier([SECRET])
1717

1818
assert.throws(() => messageVerifier.sign(null), 'Cannot sign "null" value')
1919
assert.throws(() => messageVerifier.sign(undefined), 'Cannot sign "undefined" value')
2020
})
2121

2222
test('sign an object using a secret', ({ assert }) => {
23-
const messageVerifier = new MessageVerifier(SECRET)
23+
const messageVerifier = new MessageVerifier([SECRET])
2424
const signed = messageVerifier.sign({ username: 'virk' })
2525

2626
assert.equal(base64UrlDecode(signed.split('.')[0], 'utf8'), '{"message":{"username":"virk"}}')
2727
})
2828

2929
test('sign an object with purpose', ({ assert }) => {
30-
const messageVerifier = new MessageVerifier(SECRET)
30+
const messageVerifier = new MessageVerifier([SECRET])
3131
const signed = messageVerifier.sign({ username: 'virk' }, undefined, 'login')
3232

3333
assert.equal(
@@ -37,7 +37,7 @@ test.group('MessageVerifier', () => {
3737
})
3838

3939
test('return null when unsigning non-string values', ({ assert }) => {
40-
const messageVerifier = new MessageVerifier(SECRET)
40+
const messageVerifier = new MessageVerifier([SECRET])
4141

4242
// @ts-expect-error
4343
assert.isNull(messageVerifier.unsign({}))
@@ -48,27 +48,27 @@ test.group('MessageVerifier', () => {
4848
})
4949

5050
test('unsign value', ({ assert }) => {
51-
const messageVerifier = new MessageVerifier(SECRET)
51+
const messageVerifier = new MessageVerifier([SECRET])
5252
const signed = messageVerifier.sign({ username: 'virk' })
5353
const unsigned = messageVerifier.unsign(signed)
5454

5555
assert.deepEqual(unsigned, { username: 'virk' })
5656
})
5757

5858
test('return null when unable to decode it', ({ assert }) => {
59-
const messageVerifier = new MessageVerifier(SECRET)
59+
const messageVerifier = new MessageVerifier([SECRET])
6060

6161
assert.isNull(messageVerifier.unsign('hello.world'))
6262
})
6363

6464
test('return null when hash separator is missing', ({ assert }) => {
65-
const messageVerifier = new MessageVerifier(SECRET)
65+
const messageVerifier = new MessageVerifier([SECRET])
6666

6767
assert.isNull(messageVerifier.unsign('helloworld'))
6868
})
6969

7070
test('return null when hash was touched', ({ assert }) => {
71-
const messageVerifier = new MessageVerifier(SECRET)
71+
const messageVerifier = new MessageVerifier([SECRET])
7272
const signed = messageVerifier.sign({ username: 'virk' })
7373

7474
assert.isNull(messageVerifier.unsign(signed.slice(0, -2)))

0 commit comments

Comments
 (0)