Skip to content

Commit 08829b1

Browse files
committed
feat: allow to pass encryption options
1 parent c001335 commit 08829b1

File tree

12 files changed

+217
-22
lines changed

12 files changed

+217
-22
lines changed

src/drivers/aes_256_cbc.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type {
1515
CypherText,
1616
EncryptionConfig,
1717
EncryptionDriverContract,
18+
EncryptOptions,
1819
} from '../types/main.ts'
1920
import { base64UrlDecode, base64UrlEncode } from '../base64.ts'
2021

@@ -57,7 +58,24 @@ export class AES256CBC extends BaseDriver implements EncryptionDriverContract {
5758
* You can optionally define a purpose for which the value was encrypted and
5859
* mentioning a different purpose/no purpose during decrypt will fail.
5960
*/
60-
encrypt(payload: any, expiresIn?: string | number, purpose?: string): CypherText {
61+
encrypt(payload: any, options?: EncryptOptions): CypherText
62+
encrypt(payload: any, expiresIn?: string | number, purpose?: string): CypherText
63+
encrypt(
64+
payload: any,
65+
expiresInOrOptions?: string | number | EncryptOptions,
66+
purpose?: string
67+
): CypherText {
68+
let expiresIn: string | number | undefined
69+
let actualPurpose: string | undefined
70+
71+
if (typeof expiresInOrOptions === 'object' && expiresInOrOptions !== null) {
72+
expiresIn = expiresInOrOptions.expiresIn
73+
actualPurpose = expiresInOrOptions.purpose
74+
} else {
75+
expiresIn = expiresInOrOptions
76+
actualPurpose = purpose
77+
}
78+
6179
/**
6280
* Using a random string as the iv for generating unpredictable values
6381
*/
@@ -73,7 +91,7 @@ export class AES256CBC extends BaseDriver implements EncryptionDriverContract {
7391
/**
7492
* Encoding value to a string so that we can set it on the cipher
7593
*/
76-
const plainText = new MessageBuilder().build(payload, expiresIn, purpose)
94+
const plainText = new MessageBuilder().build(payload, expiresIn, actualPurpose)
7795

7896
/**
7997
* Set final to the cipher instance and encrypt it

src/drivers/aes_256_gcm.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type {
1515
CypherText,
1616
EncryptionConfig,
1717
EncryptionDriverContract,
18+
EncryptOptions,
1819
} from '../types/main.ts'
1920

2021
export interface AES256GCMDriverConfig {
@@ -56,7 +57,24 @@ export class AES256GCM extends BaseDriver implements EncryptionDriverContract {
5657
* You can optionally define a purpose for which the value was encrypted and
5758
* mentioning a different purpose/no purpose during decrypt will fail.
5859
*/
59-
encrypt(payload: any, expiresIn?: string | number, purpose?: string): CypherText {
60+
encrypt(payload: any, options?: EncryptOptions): CypherText
61+
encrypt(payload: any, expiresIn?: string | number, purpose?: string): CypherText
62+
encrypt(
63+
payload: any,
64+
expiresInOrOptions?: string | number | EncryptOptions,
65+
purpose?: string
66+
): CypherText {
67+
let expiresIn: string | number | undefined
68+
let actualPurpose: string | undefined
69+
70+
if (typeof expiresInOrOptions === 'object' && expiresInOrOptions !== null) {
71+
expiresIn = expiresInOrOptions.expiresIn
72+
actualPurpose = expiresInOrOptions.purpose
73+
} else {
74+
expiresIn = expiresInOrOptions
75+
actualPurpose = purpose
76+
}
77+
6078
/**
6179
* Using a random string as the iv for generating unpredictable values
6280
*/
@@ -67,8 +85,10 @@ export class AES256GCM extends BaseDriver implements EncryptionDriverContract {
6785
*/
6886
const cipher = createCipheriv('aes-256-gcm', this.cryptoKey, iv)
6987

70-
if (purpose) {
71-
cipher.setAAD(Buffer.from(purpose), { plaintextLength: Buffer.byteLength(purpose) })
88+
if (actualPurpose) {
89+
cipher.setAAD(Buffer.from(actualPurpose), {
90+
plaintextLength: Buffer.byteLength(actualPurpose),
91+
})
7292
}
7393

7494
/**

src/drivers/base_driver.ts

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

88
import { createHash } from 'node:crypto'
99
import * as errors from '../exceptions.ts'
10-
import type { BaseConfig, CypherText } from '../types/main.ts'
11-
import { type Secret } from '@poppinss/utils'
10+
import type { Secret } from '@poppinss/utils'
11+
import type { BaseConfig, CypherText, EncryptOptions } from '../types/main.ts'
1212

1313
export abstract class BaseDriver {
1414
/**
@@ -62,6 +62,7 @@ export abstract class BaseDriver {
6262
* You can optionally define a purpose for which the value was encrypted and
6363
* mentioning a different purpose/no purpose during decrypt will fail.
6464
*/
65+
abstract encrypt(payload: any, options?: EncryptOptions): CypherText
6566
abstract encrypt(payload: any, expiresIn?: string | number, purpose?: string): CypherText
6667

6768
/**

src/drivers/chacha20_poly1305.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type {
1414
CypherText,
1515
EncryptionConfig,
1616
EncryptionDriverContract,
17+
EncryptOptions,
1718
} from '../types/main.ts'
1819
import { base64UrlDecode, base64UrlEncode } from '../base64.ts'
1920

@@ -56,7 +57,24 @@ export class ChaCha20Poly1305 extends BaseDriver implements EncryptionDriverCont
5657
* You can optionally define a purpose for which the value was encrypted and
5758
* mentioning a different purpose/no purpose during decrypt will fail.
5859
*/
59-
encrypt(payload: any, expiresIn?: string | number, purpose?: string): CypherText {
60+
encrypt(payload: any, options?: EncryptOptions): CypherText
61+
encrypt(payload: any, expiresIn?: string | number, purpose?: string): CypherText
62+
encrypt(
63+
payload: any,
64+
expiresInOrOptions?: string | number | EncryptOptions,
65+
purpose?: string
66+
): CypherText {
67+
let expiresIn: string | number | undefined
68+
let actualPurpose: string | undefined
69+
70+
if (typeof expiresInOrOptions === 'object' && expiresInOrOptions !== null) {
71+
expiresIn = expiresInOrOptions.expiresIn
72+
actualPurpose = expiresInOrOptions.purpose
73+
} else {
74+
expiresIn = expiresInOrOptions
75+
actualPurpose = purpose
76+
}
77+
6078
/**
6179
* Using a random string as the iv for generating unpredictable values
6280
*/
@@ -69,8 +87,10 @@ export class ChaCha20Poly1305 extends BaseDriver implements EncryptionDriverCont
6987
authTagLength: 16,
7088
})
7189

72-
if (purpose) {
73-
cipher.setAAD(Buffer.from(purpose), { plaintextLength: Buffer.byteLength(purpose) })
90+
if (actualPurpose) {
91+
cipher.setAAD(Buffer.from(actualPurpose), {
92+
plaintextLength: Buffer.byteLength(actualPurpose),
93+
})
7494
}
7595

7696
/**

src/encryption.ts

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

88
import { MessageVerifier } from './message_verifier.ts'
9-
import type { CypherText, EncryptionConfig, EncryptionDriverContract } from './types/main.ts'
9+
import type {
10+
CypherText,
11+
EncryptionConfig,
12+
EncryptionDriverContract,
13+
EncryptOptions,
14+
} from './types/main.ts'
1015

1116
/**
1217
* Encryption class that wraps a driver and manages multiple keys.
@@ -24,8 +29,25 @@ export class Encryption {
2429
/**
2530
* Encrypt a value using the first key
2631
*/
27-
encrypt(payload: any, expiresIn?: string | number, purpose?: string): CypherText {
28-
return this.#drivers[0].encrypt(payload, expiresIn, purpose)
32+
encrypt(payload: any, options?: EncryptOptions): CypherText
33+
encrypt(payload: any, expiresIn?: string | number, purpose?: string): CypherText
34+
encrypt(
35+
payload: any,
36+
expiresInOrOptions?: string | number | EncryptOptions,
37+
purpose?: string
38+
): CypherText {
39+
let expiresIn: string | number | undefined
40+
let actualPurpose: string | undefined
41+
42+
if (typeof expiresInOrOptions === 'object' && expiresInOrOptions !== null) {
43+
expiresIn = expiresInOrOptions.expiresIn
44+
actualPurpose = expiresInOrOptions.purpose
45+
} else {
46+
expiresIn = expiresInOrOptions
47+
actualPurpose = purpose
48+
}
49+
50+
return this.#drivers[0].encrypt(payload, expiresIn, actualPurpose)
2951
}
3052

3153
/**

src/encryption_manager.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { RuntimeException } from '@poppinss/utils/exception'
99
import debug from './debug.ts'
1010
import { Encryption } from './encryption.ts'
1111
import type { MessageVerifier } from './message_verifier.ts'
12-
import type { CypherText, EncryptionConfig } from './types/main.ts'
12+
import type { CypherText, EncryptionConfig, EncryptOptions } from './types/main.ts'
1313

1414
export class EncryptionManager<KnownEncrypters extends Record<string, EncryptionConfig>> {
1515
/**
@@ -75,8 +75,25 @@ export class EncryptionManager<KnownEncrypters extends Record<string, Encryption
7575
return this.use().getMessageVerifier()
7676
}
7777

78-
encrypt(payload: any, expiresIn?: string | number, purpose?: string): CypherText {
79-
return this.use().encrypt(payload, expiresIn, purpose)
78+
encrypt(payload: any, options?: EncryptOptions): CypherText
79+
encrypt(payload: any, expiresIn?: string | number, purpose?: string): CypherText
80+
encrypt(
81+
payload: any,
82+
expiresInOrOptions?: string | number | EncryptOptions,
83+
purpose?: string
84+
): CypherText {
85+
let expiresIn: string | number | undefined
86+
let actualPurpose: string | undefined
87+
88+
if (typeof expiresInOrOptions === 'object' && expiresInOrOptions !== null) {
89+
expiresIn = expiresInOrOptions.expiresIn
90+
actualPurpose = expiresInOrOptions.purpose
91+
} else {
92+
expiresIn = expiresInOrOptions
93+
actualPurpose = purpose
94+
}
95+
96+
return this.use().encrypt(payload, expiresIn, actualPurpose)
8097
}
8198

8299
decrypt<T extends any>(value: string, purpose?: string): T | null {

src/types/main.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ import { type Secret } from '@poppinss/utils'
99

1010
export type CypherText = `${string}.${string}.${string}.${string}`
1111

12+
/**
13+
* Options for the encrypt method
14+
*/
15+
export interface EncryptOptions {
16+
expiresIn?: string | number
17+
purpose?: string
18+
}
19+
1220
/**
1321
* The contract Encryption drivers should adhere to
1422
*/
@@ -27,6 +35,7 @@ export interface EncryptionDriverContract {
2735
* You can optionally define a purpose for which the value was encrypted and
2836
* mentioning a different purpose/no purpose during decrypt will fail.
2937
*/
38+
encrypt(payload: any, options?: EncryptOptions): CypherText
3039
encrypt(payload: any, expiresIn?: string | number, purpose?: string): CypherText
3140

3241
/**

tests/drivers/aes_256_cbc.spec.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ test.group('AES-256-CBC', () => {
130130

131131
test('return null when purpose is missing during decrypt', ({ assert }) => {
132132
const driver = new AES256CBC({ id: 'lanz', key: SECRET })
133-
const encrypted = driver.encrypt({ username: 'lanz' }, undefined, 'login')
133+
const encrypted = driver.encrypt({ username: 'lanz' }, { purpose: 'login' })
134134

135135
assert.isNull(driver.decrypt(encrypted))
136136
})
@@ -144,12 +144,26 @@ test.group('AES-256-CBC', () => {
144144

145145
test('return null when purpose are not same', ({ assert }) => {
146146
const driver = new AES256CBC({ id: 'lanz', key: SECRET })
147-
const encrypted = driver.encrypt({ username: 'lanz' }, undefined, 'register')
147+
const encrypted = driver.encrypt({ username: 'lanz' }, { purpose: 'register' })
148148

149149
assert.isNull(driver.decrypt(encrypted, 'login'))
150150
})
151151

152152
test('decrypt when purpose are same', ({ assert }) => {
153+
const driver = new AES256CBC({ id: 'lanz', key: SECRET })
154+
const encrypted = driver.encrypt({ username: 'lanz' }, { purpose: 'register' })
155+
156+
assert.deepEqual(driver.decrypt(encrypted, 'register'), { username: 'lanz' })
157+
})
158+
159+
test('encrypt with options object containing both expiresIn and purpose', ({ assert }) => {
160+
const driver = new AES256CBC({ id: 'lanz', key: SECRET })
161+
const encrypted = driver.encrypt({ username: 'lanz' }, { expiresIn: '1h', purpose: 'register' })
162+
163+
assert.deepEqual(driver.decrypt(encrypted, 'register'), { username: 'lanz' })
164+
})
165+
166+
test('backward compatibility: encrypt with positional arguments', ({ assert }) => {
153167
const driver = new AES256CBC({ id: 'lanz', key: SECRET })
154168
const encrypted = driver.encrypt({ username: 'lanz' }, undefined, 'register')
155169

tests/drivers/aes_256_gcm.spec.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ test.group('AES-256-GCM', () => {
137137

138138
test('return null when purpose is missing during decrypt', ({ assert }) => {
139139
const driver = new AES256GCM({ id: 'lanz', key: SECRET })
140-
const encrypted = driver.encrypt({ username: 'lanz' }, undefined, 'login')
140+
const encrypted = driver.encrypt({ username: 'lanz' }, { purpose: 'login' })
141141

142142
assert.isNull(driver.decrypt(encrypted))
143143
})
@@ -151,12 +151,26 @@ test.group('AES-256-GCM', () => {
151151

152152
test('return null when purpose are not same', ({ assert }) => {
153153
const driver = new AES256GCM({ id: 'lanz', key: SECRET })
154-
const encrypted = driver.encrypt({ username: 'lanz' }, undefined, 'register')
154+
const encrypted = driver.encrypt({ username: 'lanz' }, { purpose: 'register' })
155155

156156
assert.isNull(driver.decrypt(encrypted, 'login'))
157157
})
158158

159159
test('decrypt when purpose are same', ({ assert }) => {
160+
const driver = new AES256GCM({ id: 'lanz', key: SECRET })
161+
const encrypted = driver.encrypt({ username: 'lanz' }, { purpose: 'register' })
162+
163+
assert.deepEqual(driver.decrypt(encrypted, 'register'), { username: 'lanz' })
164+
})
165+
166+
test('encrypt with options object containing both expiresIn and purpose', ({ assert }) => {
167+
const driver = new AES256GCM({ id: 'lanz', key: SECRET })
168+
const encrypted = driver.encrypt({ username: 'lanz' }, { expiresIn: '1h', purpose: 'register' })
169+
170+
assert.deepEqual(driver.decrypt(encrypted, 'register'), { username: 'lanz' })
171+
})
172+
173+
test('backward compatibility: encrypt with positional arguments', ({ assert }) => {
160174
const driver = new AES256GCM({ id: 'lanz', key: SECRET })
161175
const encrypted = driver.encrypt({ username: 'lanz' }, undefined, 'register')
162176

tests/drivers/chacha20_poly1305.spec.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ test.group('ChaCha20-Poly1305', () => {
130130

131131
test('return null when purpose is missing during decrypt', ({ assert }) => {
132132
const driver = new ChaCha20Poly1305({ id: 'lanz', key: SECRET })
133-
const encrypted = driver.encrypt({ username: 'lanz' }, undefined, 'login')
133+
const encrypted = driver.encrypt({ username: 'lanz' }, { purpose: 'login' })
134134

135135
assert.isNull(driver.decrypt(encrypted))
136136
})
@@ -144,12 +144,26 @@ test.group('ChaCha20-Poly1305', () => {
144144

145145
test('return null when purpose are not same', ({ assert }) => {
146146
const driver = new ChaCha20Poly1305({ id: 'lanz', key: SECRET })
147-
const encrypted = driver.encrypt({ username: 'lanz' }, undefined, 'register')
147+
const encrypted = driver.encrypt({ username: 'lanz' }, { purpose: 'register' })
148148

149149
assert.isNull(driver.decrypt(encrypted, 'login'))
150150
})
151151

152152
test('decrypt when purpose are same', ({ assert }) => {
153+
const driver = new ChaCha20Poly1305({ id: 'lanz', key: SECRET })
154+
const encrypted = driver.encrypt({ username: 'lanz' }, { purpose: 'register' })
155+
156+
assert.deepEqual(driver.decrypt(encrypted, 'register'), { username: 'lanz' })
157+
})
158+
159+
test('encrypt with options object containing both expiresIn and purpose', ({ assert }) => {
160+
const driver = new ChaCha20Poly1305({ id: 'lanz', key: SECRET })
161+
const encrypted = driver.encrypt({ username: 'lanz' }, { expiresIn: '1h', purpose: 'register' })
162+
163+
assert.deepEqual(driver.decrypt(encrypted, 'register'), { username: 'lanz' })
164+
})
165+
166+
test('backward compatibility: encrypt with positional arguments', ({ assert }) => {
153167
const driver = new ChaCha20Poly1305({ id: 'lanz', key: SECRET })
154168
const encrypted = driver.encrypt({ username: 'lanz' }, undefined, 'register')
155169

0 commit comments

Comments
 (0)