feat: add sha256 and hmac_sha256 filters for cryptographic operations (#889)

This commit is contained in:
Vladimir Filonov
2026-05-03 12:03:26 +08:00
committed by GitHub
parent 34877950bf
commit 1c816d4fc3
10 changed files with 226 additions and 2 deletions
+45
View File
@@ -0,0 +1,45 @@
import { webcrypto } from 'crypto'
import * as cryptoImpl from './crypto-impl-browser'
describe('crypto-impl/browser', function () {
beforeEach(function () {
Object.defineProperty(global, 'crypto', {
value: webcrypto,
writable: true,
configurable: true
})
})
afterEach(function () {
delete (global as any).crypto
})
describe('#sha256()', function () {
it('should hash the Shopify reference example', async function () {
expect(await cryptoImpl.sha256('Polyjuice'))
.toBe('44ac1d7a2936e30a5de07082fd65d6fe9b1fb658a1a98bfe65bc5959beac5dd0')
})
it('should hash an empty string', async function () {
expect(await cryptoImpl.sha256(''))
.toBe('e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855')
})
it('should hash unicode characters', async function () {
expect(await cryptoImpl.sha256('你好世界'))
.toBe('beca6335b20ff57ccc47403ef4d9e0b8fccb4442b3151c2e7d50050673d43172')
})
})
describe('#hmacSha256()', function () {
it('should hash the Shopify reference example', async function () {
expect(await cryptoImpl.hmacSha256('Polyjuice', 'Polina'))
.toBe('8e0d5d65cff1242a4af66c8f4a32854fd5fb80edcc8aabe9b302b29c7c71dc20')
})
it('should hash an empty message with a key', async function () {
expect(await cryptoImpl.hmacSha256('', 'key'))
.toBe('5d5d139563c95b5967b9bd9a8c9b233a9dedb45072794cd232dc1b74832607d0')
})
})
})
+27
View File
@@ -0,0 +1,27 @@
function bufferToHex (buffer: ArrayBuffer): string {
const bytes = new Uint8Array(buffer)
let hex = ''
for (let i = 0; i < bytes.length; i++) {
hex += bytes[i].toString(16).padStart(2, '0')
}
return hex
}
export async function sha256 (str: string): Promise<string> {
const data = new TextEncoder().encode(str)
const digest = await crypto.subtle.digest('SHA-256', data)
return bufferToHex(digest)
}
export async function hmacSha256 (str: string, key: string): Promise<string> {
const encoder = new TextEncoder()
const cryptoKey = await crypto.subtle.importKey(
'raw',
encoder.encode(key),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
)
const signature = await crypto.subtle.sign('HMAC', cryptoKey, encoder.encode(str))
return bufferToHex(signature)
}
+9
View File
@@ -0,0 +1,9 @@
import { createHash, createHmac } from 'crypto'
export function sha256 (str: string): string {
return createHash('sha256').update(str, 'utf8').digest('hex')
}
export function hmacSha256 (str: string, key: string): string {
return createHmac('sha256', key).update(str, 'utf8').digest('hex')
}
+22
View File
@@ -0,0 +1,22 @@
/**
* Crypto related filters
*
* Implements sha256 and hmac_sha256 filters for Shopify compatibility
*/
import { FilterImpl } from '../template'
import { stringify } from '../util'
import { sha256 as sha256Impl, hmacSha256 as hmacSha256Impl } from './crypto-impl'
export function sha256 (this: FilterImpl, value: unknown): string | Promise<string> {
const str = stringify(value)
this.context.memoryLimit.use(str.length)
return sha256Impl(str)
}
export function hmac_sha256 (this: FilterImpl, value: unknown, key: unknown): string | Promise<string> {
const str = stringify(value)
const keyStr = stringify(key)
this.context.memoryLimit.use(str.length + keyStr.length)
return hmacSha256Impl(str, keyStr)
}
+2
View File
@@ -5,6 +5,7 @@ import * as arrayFilters from './array'
import * as dateFilters from './date'
import * as stringFilters from './string'
import * as base64Filters from './base64'
import * as cryptoFilters from './crypto'
import misc from './misc'
import { FilterImplOptions } from '../template'
@@ -16,5 +17,6 @@ export const filters: Record<string, FilterImplOptions> = {
...dateFilters,
...stringFilters,
...base64Filters,
...cryptoFilters,
...misc
}