Add JWT
ci/woodpecker/push/test Pipeline failed

Closes #13
This commit is contained in:
2024-05-15 10:36:54 +02:00
parent de864ebab1
commit 0347f30bd0
4 changed files with 124 additions and 0 deletions
+1
View File
@@ -1,2 +1,3 @@
export * as signature from 'signature'
export * as boxes from 'boxes'
export * as JWT from 'jwt'
+1
View File
@@ -14,6 +14,7 @@
},
"dependencies": {
"jose": "^5.3.0",
"result": "git+git@git.pband.ch:typescript/result",
"misc": "git+git@git.pband.ch:typescript/misc",
"zxcvbn": "^4.4.2"
+81
View File
@@ -0,0 +1,81 @@
import {Result} from 'result'
import * as jose from 'jose'
export type JWTstring = `${string}.${string}.${string}`
export type JWTalgorithm = "HS256" | "HS512" | "ES256" | "ES512" | "EdDSA"
export type Key = jose.KeyLike | Uint8Array
/** KeyPair for asymmetric algorithms, [PrivKey, PubKey] */
export type KeyPair = [Key, Key]
export class JWTcontext {
public constructor(
private readonly key: Key | KeyPair,
private readonly alg: JWTalgorithm,
) {}
public static async gen_key(alg: JWTalgorithm, extractable: boolean = false) : Promise<Key | KeyPair> {
switch (alg) {
case "HS256":
case "HS512":
return jose.generateSecret(alg, { extractable })
case "EdDSA":
case "ES256":
case "ES512":
{
const key = await jose.generateKeyPair(alg)
return [key.privateKey, key.publicKey]
}
}
}
public async sign<T extends jose.JWTPayload>(message: T, set_issued: boolean = false, exp?: number | string | Date, audience?: string | string[], issuer?: string): Promise<JWT<T>> {
let jwt = new jose.SignJWT({payload: message}).setProtectedHeader({ alg: this.alg })
if (set_issued) jwt = jwt.setIssuedAt()
if (issuer !== undefined) jwt = jwt.setIssuer(issuer)
if (audience !== undefined) jwt = jwt.setAudience(audience)
if (exp !== undefined) jwt = jwt.setExpirationTime(exp)
const key = this.get_key(true)
const res = await jwt.sign(key) as JWTstring
return new JWT<T>(res)
}
public async verify<T>(jwt: JWT<T>, audience?: string | string[], issuer?: string | string[]): Promise<Result<T>> {
const key = this.get_key(false)
try {
let payload = await jose.jwtVerify(jwt.jwt, key)
return Result.ok(payload.payload.payload as T)
} catch(_) {}
return Result.error([])
}
private get_key(sign: boolean) : Key {
switch (this.alg) {
case "HS256":
case "HS512":
return this.key as Key
case "ES256":
case "ES512":
case "EdDSA":
return (this.key as KeyPair)[sign ? 0 : 1]
}
}
}
export class JWT<T> {
public constructor(
public readonly _jwt: `${string}.${string}.${string}`
) {}
public get payload() : T {
return jose.decodeJwt(this.jwt).payload as T
}
public get jwt() : JWTstring {
return this._jwt
}
}
+41
View File
@@ -0,0 +1,41 @@
import {beforeAll, expect, test} from 'bun:test'
import {JWTcontext, JWTalgorithm} from '../src/jwt'
const algs: JWTalgorithm[] = ["HS256", "HS512", "ES256", "ES512", "EdDSA"]
const contexts: Map<JWTalgorithm, JWTcontext> = new Map()
beforeAll(async () => {
for (const alg of algs) {
const key = await JWTcontext.gen_key(alg as JWTalgorithm)
expect(key).not.toBeUndefined()
const context = new JWTcontext(key, alg as JWTalgorithm)
contexts.set(alg as JWTalgorithm, context)
}
})
test('Base case', async () => {
let payload = {
yeet: "yaat",
lol: "yes"
}
// @ts-expect-error : idc bout your iterator thingy >:(
for (const context of contexts.values()) {
const jwt = await context.sign(payload, true, "2 days", "pascal", "server")
const decoded = jwt.payload
expect(decoded).toEqual(payload)
const verified = (await context.verify(jwt, "pascal", "server")).expect("Should verify the JWT")
expect(verified).toEqual(payload)
}
})
test.todo("Multiple audience can be verified")
test.todo("Multiple issuer can be verified")
test.todo("Wrong audience is rejected")
test.todo("Wrong issuer is rejected")
test.todo("Expired JWT is rejected")
test.todo("Wrong key won't decrypt")
test.todo("tampered JWT are rejected (test 3 parts) (TODO : decode ?)")