@open-mercato/shared 0.4.11-develop.1530.964c5df5df → 0.4.11-develop.1531.b0e4c16f3d
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/dist/lib/auth/jwt.js
CHANGED
|
@@ -21,8 +21,16 @@ function verifyJwt(token, secret = process.env.JWT_SECRET) {
|
|
|
21
21
|
const [h, p, s] = parts;
|
|
22
22
|
const data = `${h}.${p}`;
|
|
23
23
|
const expected = base64url(crypto.createHmac("sha256", secret).update(data).digest());
|
|
24
|
-
|
|
25
|
-
const
|
|
24
|
+
const providedSignature = Buffer.from(s);
|
|
25
|
+
const expectedSignature = Buffer.from(expected);
|
|
26
|
+
if (providedSignature.length !== expectedSignature.length) return null;
|
|
27
|
+
if (!crypto.timingSafeEqual(providedSignature, expectedSignature)) return null;
|
|
28
|
+
let payload;
|
|
29
|
+
try {
|
|
30
|
+
payload = JSON.parse(Buffer.from(p, "base64").toString("utf8"));
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
26
34
|
const now = Math.floor(Date.now() / 1e3);
|
|
27
35
|
if (payload.exp && now > payload.exp) return null;
|
|
28
36
|
return payload;
|
package/dist/lib/auth/jwt.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/auth/jwt.ts"],
|
|
4
|
-
"sourcesContent": ["import crypto from 'node:crypto'\n\nfunction base64url(input: Buffer | string) {\n return (typeof input === 'string' ? Buffer.from(input) : input)\n .toString('base64')\n .replace(/=/g, '')\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n}\n\nexport type JwtPayload = Record<string, any>\n\nexport function signJwt(payload: JwtPayload, secret = process.env.JWT_SECRET!, expiresInSec = 60 * 60 * 8) {\n if (!secret) throw new Error('JWT_SECRET is not set')\n const header = { alg: 'HS256', typ: 'JWT' }\n const now = Math.floor(Date.now() / 1000)\n const body = { iat: now, exp: now + expiresInSec, ...payload }\n const encHeader = base64url(JSON.stringify(header))\n const encBody = base64url(JSON.stringify(body))\n const data = `${encHeader}.${encBody}`\n const sig = crypto.createHmac('sha256', secret).update(data).digest()\n const encSig = base64url(sig)\n return `${data}.${encSig}`\n}\n\nexport function verifyJwt(token: string, secret = process.env.JWT_SECRET!) {\n if (!secret) throw new Error('JWT_SECRET is not set')\n const parts = token.split('.')\n if (parts.length !== 3) return null\n const [h, p, s] = parts\n const data = `${h}.${p}`\n const expected = base64url(crypto.createHmac('sha256', secret).update(data).digest())\n
|
|
5
|
-
"mappings": "AAAA,OAAO,YAAY;AAEnB,SAAS,UAAU,OAAwB;AACzC,UAAQ,OAAO,UAAU,WAAW,OAAO,KAAK,KAAK,IAAI,OACtD,SAAS,QAAQ,EACjB,QAAQ,MAAM,EAAE,EAChB,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,GAAG;AACvB;AAIO,SAAS,QAAQ,SAAqB,SAAS,QAAQ,IAAI,YAAa,eAAe,KAAK,KAAK,GAAG;AACzG,MAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,uBAAuB;AACpD,QAAM,SAAS,EAAE,KAAK,SAAS,KAAK,MAAM;AAC1C,QAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AACxC,QAAM,OAAO,EAAE,KAAK,KAAK,KAAK,MAAM,cAAc,GAAG,QAAQ;AAC7D,QAAM,YAAY,UAAU,KAAK,UAAU,MAAM,CAAC;AAClD,QAAM,UAAU,UAAU,KAAK,UAAU,IAAI,CAAC;AAC9C,QAAM,OAAO,GAAG,SAAS,IAAI,OAAO;AACpC,QAAM,MAAM,OAAO,WAAW,UAAU,MAAM,EAAE,OAAO,IAAI,EAAE,OAAO;AACpE,QAAM,SAAS,UAAU,GAAG;AAC5B,SAAO,GAAG,IAAI,IAAI,MAAM;AAC1B;AAEO,SAAS,UAAU,OAAe,SAAS,QAAQ,IAAI,YAAa;AACzE,MAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,uBAAuB;AACpD,QAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,QAAM,CAAC,GAAG,GAAG,CAAC,IAAI;AAClB,QAAM,OAAO,GAAG,CAAC,IAAI,CAAC;AACtB,QAAM,WAAW,UAAU,OAAO,WAAW,UAAU,MAAM,EAAE,OAAO,IAAI,EAAE,OAAO,CAAC;AACpF,
|
|
4
|
+
"sourcesContent": ["import crypto from 'node:crypto'\n\nfunction base64url(input: Buffer | string) {\n return (typeof input === 'string' ? Buffer.from(input) : input)\n .toString('base64')\n .replace(/=/g, '')\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n}\n\nexport type JwtPayload = Record<string, any>\n\nexport function signJwt(payload: JwtPayload, secret = process.env.JWT_SECRET!, expiresInSec = 60 * 60 * 8) {\n if (!secret) throw new Error('JWT_SECRET is not set')\n const header = { alg: 'HS256', typ: 'JWT' }\n const now = Math.floor(Date.now() / 1000)\n const body = { iat: now, exp: now + expiresInSec, ...payload }\n const encHeader = base64url(JSON.stringify(header))\n const encBody = base64url(JSON.stringify(body))\n const data = `${encHeader}.${encBody}`\n const sig = crypto.createHmac('sha256', secret).update(data).digest()\n const encSig = base64url(sig)\n return `${data}.${encSig}`\n}\n\nexport function verifyJwt(token: string, secret = process.env.JWT_SECRET!) {\n if (!secret) throw new Error('JWT_SECRET is not set')\n const parts = token.split('.')\n if (parts.length !== 3) return null\n const [h, p, s] = parts\n const data = `${h}.${p}`\n const expected = base64url(crypto.createHmac('sha256', secret).update(data).digest())\n const providedSignature = Buffer.from(s)\n const expectedSignature = Buffer.from(expected)\n if (providedSignature.length !== expectedSignature.length) return null\n if (!crypto.timingSafeEqual(providedSignature, expectedSignature)) return null\n let payload: JwtPayload\n try {\n payload = JSON.parse(Buffer.from(p, 'base64').toString('utf8'))\n } catch {\n return null\n }\n const now = Math.floor(Date.now() / 1000)\n if (payload.exp && now > payload.exp) return null\n return payload\n}\n"],
|
|
5
|
+
"mappings": "AAAA,OAAO,YAAY;AAEnB,SAAS,UAAU,OAAwB;AACzC,UAAQ,OAAO,UAAU,WAAW,OAAO,KAAK,KAAK,IAAI,OACtD,SAAS,QAAQ,EACjB,QAAQ,MAAM,EAAE,EAChB,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,GAAG;AACvB;AAIO,SAAS,QAAQ,SAAqB,SAAS,QAAQ,IAAI,YAAa,eAAe,KAAK,KAAK,GAAG;AACzG,MAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,uBAAuB;AACpD,QAAM,SAAS,EAAE,KAAK,SAAS,KAAK,MAAM;AAC1C,QAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AACxC,QAAM,OAAO,EAAE,KAAK,KAAK,KAAK,MAAM,cAAc,GAAG,QAAQ;AAC7D,QAAM,YAAY,UAAU,KAAK,UAAU,MAAM,CAAC;AAClD,QAAM,UAAU,UAAU,KAAK,UAAU,IAAI,CAAC;AAC9C,QAAM,OAAO,GAAG,SAAS,IAAI,OAAO;AACpC,QAAM,MAAM,OAAO,WAAW,UAAU,MAAM,EAAE,OAAO,IAAI,EAAE,OAAO;AACpE,QAAM,SAAS,UAAU,GAAG;AAC5B,SAAO,GAAG,IAAI,IAAI,MAAM;AAC1B;AAEO,SAAS,UAAU,OAAe,SAAS,QAAQ,IAAI,YAAa;AACzE,MAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,uBAAuB;AACpD,QAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,QAAM,CAAC,GAAG,GAAG,CAAC,IAAI;AAClB,QAAM,OAAO,GAAG,CAAC,IAAI,CAAC;AACtB,QAAM,WAAW,UAAU,OAAO,WAAW,UAAU,MAAM,EAAE,OAAO,IAAI,EAAE,OAAO,CAAC;AACpF,QAAM,oBAAoB,OAAO,KAAK,CAAC;AACvC,QAAM,oBAAoB,OAAO,KAAK,QAAQ;AAC9C,MAAI,kBAAkB,WAAW,kBAAkB,OAAQ,QAAO;AAClE,MAAI,CAAC,OAAO,gBAAgB,mBAAmB,iBAAiB,EAAG,QAAO;AAC1E,MAAI;AACJ,MAAI;AACF,cAAU,KAAK,MAAM,OAAO,KAAK,GAAG,QAAQ,EAAE,SAAS,MAAM,CAAC;AAAA,EAChE,QAAQ;AACN,WAAO;AAAA,EACT;AACA,QAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AACxC,MAAI,QAAQ,OAAO,MAAM,QAAQ,IAAK,QAAO;AAC7C,SAAO;AACT;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/lib/version.js
CHANGED
package/dist/lib/version.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/version.ts"],
|
|
4
|
-
"sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.4.11-develop.
|
|
4
|
+
"sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.4.11-develop.1531.b0e4c16f3d'\nexport const appVersion = APP_VERSION\n"],
|
|
5
5
|
"mappings": "AACO,MAAM,cAAc;AACpB,MAAM,aAAa;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import crypto from 'node:crypto'
|
|
2
|
+
|
|
3
|
+
import { signJwt, verifyJwt } from '../jwt'
|
|
4
|
+
|
|
5
|
+
function base64url(input: Buffer | string): string {
|
|
6
|
+
return (typeof input === 'string' ? Buffer.from(input) : input)
|
|
7
|
+
.toString('base64')
|
|
8
|
+
.replace(/=/g, '')
|
|
9
|
+
.replace(/\+/g, '-')
|
|
10
|
+
.replace(/\//g, '_')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function signTokenParts(header: string, payload: string, secret: string): string {
|
|
14
|
+
return base64url(crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest())
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe('jwt helpers', () => {
|
|
18
|
+
const secret = 'test-secret'
|
|
19
|
+
const now = new Date('2026-04-11T12:00:00.000Z')
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
jest.spyOn(Date, 'now').mockReturnValue(now.getTime())
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
jest.restoreAllMocks()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('signs and verifies payloads with issued and expiry timestamps', () => {
|
|
30
|
+
const token = signJwt({ sub: 'user-1', roles: ['admin'] }, secret, 300)
|
|
31
|
+
|
|
32
|
+
expect(verifyJwt(token, secret)).toEqual({
|
|
33
|
+
sub: 'user-1',
|
|
34
|
+
roles: ['admin'],
|
|
35
|
+
iat: Math.floor(now.getTime() / 1000),
|
|
36
|
+
exp: Math.floor(now.getTime() / 1000) + 300,
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('rejects tokens with tampered payloads', () => {
|
|
41
|
+
const token = signJwt({ sub: 'user-1' }, secret, 300)
|
|
42
|
+
const [header, , signature] = token.split('.')
|
|
43
|
+
const tamperedPayload = base64url(
|
|
44
|
+
JSON.stringify({
|
|
45
|
+
sub: 'user-2',
|
|
46
|
+
iat: Math.floor(now.getTime() / 1000),
|
|
47
|
+
exp: Math.floor(now.getTime() / 1000) + 300,
|
|
48
|
+
})
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
expect(verifyJwt(`${header}.${tamperedPayload}.${signature}`, secret)).toBeNull()
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('rejects expired tokens', () => {
|
|
55
|
+
const token = signJwt({ sub: 'user-1' }, secret, 1)
|
|
56
|
+
|
|
57
|
+
jest.spyOn(Date, 'now').mockReturnValue(now.getTime() + 3_000)
|
|
58
|
+
|
|
59
|
+
expect(verifyJwt(token, secret)).toBeNull()
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('returns null for malformed signatures', () => {
|
|
63
|
+
const token = signJwt({ sub: 'user-1' }, secret, 300)
|
|
64
|
+
const [header, payload] = token.split('.')
|
|
65
|
+
|
|
66
|
+
expect(verifyJwt(`${header}.${payload}.x`, secret)).toBeNull()
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('returns null for signed payloads that are not valid JSON', () => {
|
|
70
|
+
const header = base64url(JSON.stringify({ alg: 'HS256', typ: 'JWT' }))
|
|
71
|
+
const payload = base64url('not-json')
|
|
72
|
+
const signature = signTokenParts(header, payload, secret)
|
|
73
|
+
|
|
74
|
+
expect(verifyJwt(`${header}.${payload}.${signature}`, secret)).toBeNull()
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('throws when the JWT secret is missing', () => {
|
|
78
|
+
expect(() => signJwt({ sub: 'user-1' }, '')).toThrow('JWT_SECRET is not set')
|
|
79
|
+
expect(() => verifyJwt('header.payload.signature', '')).toThrow('JWT_SECRET is not set')
|
|
80
|
+
})
|
|
81
|
+
})
|
package/src/lib/auth/jwt.ts
CHANGED
|
@@ -30,10 +30,17 @@ export function verifyJwt(token: string, secret = process.env.JWT_SECRET!) {
|
|
|
30
30
|
const [h, p, s] = parts
|
|
31
31
|
const data = `${h}.${p}`
|
|
32
32
|
const expected = base64url(crypto.createHmac('sha256', secret).update(data).digest())
|
|
33
|
-
|
|
34
|
-
const
|
|
33
|
+
const providedSignature = Buffer.from(s)
|
|
34
|
+
const expectedSignature = Buffer.from(expected)
|
|
35
|
+
if (providedSignature.length !== expectedSignature.length) return null
|
|
36
|
+
if (!crypto.timingSafeEqual(providedSignature, expectedSignature)) return null
|
|
37
|
+
let payload: JwtPayload
|
|
38
|
+
try {
|
|
39
|
+
payload = JSON.parse(Buffer.from(p, 'base64').toString('utf8'))
|
|
40
|
+
} catch {
|
|
41
|
+
return null
|
|
42
|
+
}
|
|
35
43
|
const now = Math.floor(Date.now() / 1000)
|
|
36
44
|
if (payload.exp && now > payload.exp) return null
|
|
37
45
|
return payload
|
|
38
46
|
}
|
|
39
|
-
|