0
0
mirror of https://github.com/honojs/hono.git synced 2024-11-29 17:46:30 +01:00
hono/deno_dist/utils/jwt/jwt.ts
2022-07-02 15:09:45 +09:00

142 lines
3.1 KiB
TypeScript

import {
utf8ToUint8Array,
encodeBase64URL,
arrayBufferToBase64URL,
decodeBase64URL,
} from '../../utils/encode.ts'
import { AlgorithmTypes } from './types.ts'
import {
JwtTokenInvalid,
JwtTokenNotBefore,
JwtTokenExpired,
JwtTokenSignatureMismatched,
JwtAlorithmNotImplemented,
} from './types.ts'
interface AlgorithmParams {
name: string
namedCurve?: string
hash?: {
name: string
}
}
enum CryptoKeyFormat {
RAW = 'raw',
PKCS8 = 'pkcs8',
SPKI = 'spki',
JWK = 'jwk',
}
enum CryptoKeyUsage {
Ecrypt = 'encrypt',
Decrypt = 'decrypt',
Sign = 'sign',
Verify = 'verify',
Deriverkey = 'deriveKey',
DeriveBits = 'deriveBits',
WrapKey = 'wrapKey',
UnwrapKey = 'unwrapKey',
}
const param = (name: AlgorithmTypes): AlgorithmParams => {
switch (name.toUpperCase()) {
case 'HS256':
return {
name: 'HMAC',
hash: {
name: 'SHA-256',
},
}
case 'HS384':
return {
name: 'HMAC',
hash: {
name: 'SHA-384',
},
}
case 'HS512':
return {
name: 'HMAC',
hash: {
name: 'SHA-512',
},
}
default:
throw new JwtAlorithmNotImplemented(name)
}
}
const signing = async (
data: string,
secret: string,
alg: AlgorithmTypes = AlgorithmTypes.HS256
): Promise<ArrayBuffer> => {
const cryptoKey = await crypto.subtle.importKey(
CryptoKeyFormat.RAW,
utf8ToUint8Array(secret),
param(alg),
false,
[CryptoKeyUsage.Sign]
)
return await crypto.subtle.sign(param(alg), cryptoKey, utf8ToUint8Array(data))
}
export const sign = async (
payload: unknown,
secret: string,
alg: AlgorithmTypes = AlgorithmTypes.HS256
): Promise<string> => {
const encodedPayload = await encodeBase64URL(JSON.stringify(payload))
const encodedHeader = await encodeBase64URL(JSON.stringify({ alg, typ: 'JWT' }))
const partialToken = `${encodedHeader}.${encodedPayload}`
const signature: string = await arrayBufferToBase64URL(await signing(partialToken, secret, alg))
return `${partialToken}.${signature}`
}
export const verify = async (
token: string,
secret: string,
alg: AlgorithmTypes = AlgorithmTypes.HS256
): Promise<boolean> => {
const tokenParts = token.split('.')
if (tokenParts.length !== 3) {
throw new JwtTokenInvalid(token)
}
const { payload } = decode(token)
if (payload.nbf && payload.nbf > Math.floor(Date.now() / 1000)) {
throw new JwtTokenNotBefore(token)
}
if (payload.exp && payload.exp <= Math.floor(Date.now() / 1000)) {
throw new JwtTokenExpired(token)
}
const signature: string = await arrayBufferToBase64URL(
await signing(tokenParts.slice(0, 2).join('.'), secret, alg)
)
if (signature !== tokenParts[2]) {
throw new JwtTokenSignatureMismatched(token)
}
return true
}
// eslint-disable-next-line
export const decode = (token: string): { header: any; payload: any } => {
try {
const [h, p] = token.split('.')
const header = JSON.parse(decodeBase64URL(h))
const payload = JSON.parse(decodeBase64URL(p))
return {
header,
payload,
}
} catch (e) {
throw new JwtTokenInvalid(token)
}
}