update jwt
This commit is contained in:
@@ -0,0 +1,65 @@
|
|||||||
|
import { describe, it, expect } from 'vitest'
|
||||||
|
import { decodeJwtPayload, extractRoleFromJwt } from '../jwt'
|
||||||
|
|
||||||
|
/** 构造一个 fake JWT(header.payload.signature),payload 为给定对象 */
|
||||||
|
function fakeJwt(payload: Record<string, unknown>): string {
|
||||||
|
const header = btoa(JSON.stringify({ alg: 'HS256', typ: 'JWT' }))
|
||||||
|
const body = btoa(JSON.stringify(payload))
|
||||||
|
return `${header}.${body}.fake-signature`
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('decodeJwtPayload', () => {
|
||||||
|
it('correctly decodes a standard JWT payload', () => {
|
||||||
|
const payload = { uid: 1, role: 'administrator', exp: 9999999999 }
|
||||||
|
const result = decodeJwtPayload(fakeJwt(payload))
|
||||||
|
|
||||||
|
expect(result).toEqual(payload)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns null for non-three-segment string', () => {
|
||||||
|
expect(decodeJwtPayload('only-one-part')).toBeNull()
|
||||||
|
expect(decodeJwtPayload('two.parts')).toBeNull()
|
||||||
|
expect(decodeJwtPayload('')).toBeNull()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns null for invalid base64 payload', () => {
|
||||||
|
expect(decodeJwtPayload('a.!!!invalid!!!.c')).toBeNull()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns null for non-JSON payload', () => {
|
||||||
|
const nonJson = `a.${btoa('not json')}.c`
|
||||||
|
expect(decodeJwtPayload(nonJson)).toBeNull()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles base64url encoding (- and _ characters)', () => {
|
||||||
|
// base64url uses - instead of + and _ instead of /
|
||||||
|
const payload = { data: 'test+value/here' }
|
||||||
|
const standard = btoa(JSON.stringify(payload))
|
||||||
|
const urlSafe = standard.replace(/\+/g, '-').replace(/\//g, '_')
|
||||||
|
const token = `header.${urlSafe}.sig`
|
||||||
|
|
||||||
|
expect(decodeJwtPayload(token)).toEqual(payload)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('extractRoleFromJwt', () => {
|
||||||
|
it('extracts top-level role string', () => {
|
||||||
|
expect(extractRoleFromJwt(fakeJwt({ role: 'administrator' }))).toBe('administrator')
|
||||||
|
expect(extractRoleFromJwt(fakeJwt({ role: 'accessor' }))).toBe('accessor')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns null when role field is missing', () => {
|
||||||
|
expect(extractRoleFromJwt(fakeJwt({ uid: 1 }))).toBeNull()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns null when role is not a string', () => {
|
||||||
|
expect(extractRoleFromJwt(fakeJwt({ role: 123 }))).toBeNull()
|
||||||
|
expect(extractRoleFromJwt(fakeJwt({ role: null }))).toBeNull()
|
||||||
|
expect(extractRoleFromJwt(fakeJwt({ role: { name: 'admin' } }))).toBeNull()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns null for invalid token', () => {
|
||||||
|
expect(extractRoleFromJwt('garbage')).toBeNull()
|
||||||
|
expect(extractRoleFromJwt('')).toBeNull()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* 解码 JWT payload(不验证签名,签名由服务端验证)。
|
||||||
|
* 返回 payload 对象,解码失败返回 null。
|
||||||
|
*/
|
||||||
|
export function decodeJwtPayload(token: string): Record<string, unknown> | null {
|
||||||
|
try {
|
||||||
|
const parts = token.split('.')
|
||||||
|
if (parts.length !== 3) return null
|
||||||
|
const base64 = parts[1].replace(/-/g, '+').replace(/_/g, '/')
|
||||||
|
const padded = base64 + '='.repeat((4 - base64.length % 4) % 4)
|
||||||
|
const decoded = atob(padded)
|
||||||
|
return JSON.parse(decoded)
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 JWT token 中提取用户角色。
|
||||||
|
* 返回角色字符串(如 'administrator'),提取失败返回 null。
|
||||||
|
*/
|
||||||
|
export function extractRoleFromJwt(token: string): string | null {
|
||||||
|
const payload = decodeJwtPayload(token)
|
||||||
|
if (!payload || typeof payload.role !== 'string') return null
|
||||||
|
return payload.role
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user