Skip to content

Commit

Permalink
Key-Redemption
Browse files Browse the repository at this point in the history
  • Loading branch information
tbergmueller committed Apr 23, 2024
1 parent 2015d89 commit e48df78
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 10 deletions.
33 changes: 25 additions & 8 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { V4, errors } from 'paseto';
import NodeCache from 'node-cache';


type AttestationPayload = {
slid: string;
sub: string | undefined;
Expand All @@ -10,6 +11,7 @@ type AttestationPayload = {

interface DecodeParameters {
ignoreExpiry?: boolean;
noRedeem?: boolean;
}

class AttestationError extends Error {
Expand All @@ -25,7 +27,8 @@ class AttestationError extends Error {

class Attestation {
constructor(
private readonly payload: AttestationPayload
private readonly payload: AttestationPayload,
private readonly token: string
) {

}
Expand All @@ -40,31 +43,37 @@ class Attestation {

public isFraud(): boolean {
return this.payload?.result== "COUNTERFEIT"
}

public getHash(): string {
const crypto = require('crypto');
const hash = crypto.createHash('sha256').update(this.token).digest('hex');
return hash;
}
}

class AttestationManager {
private cache: NodeCache = new NodeCache();
private keyCache: NodeCache = new NodeCache();
private keyServerUrl: string = "https://sip-keys.authenticvision.com/v4";
private redeemedCache: NodeCache = new NodeCache();

constructor() {

}

public addKey(keyId: string, publicKey: string) {
this.cache.set(keyId, publicKey);
this.keyCache.set(keyId, publicKey);
}

private async getSipKeyHttps(keyId:string): Promise<string> {
const cachedKey = this.cache.get<string>(keyId);
const cachedKey = this.keyCache.get<string>(keyId);
if (cachedKey) {
return cachedKey;
}

const response = await fetch(`${this.keyServerUrl}/${keyId}`);
const key = await response.text();
this.cache.set(keyId, key);
this.keyCache.set(keyId, key);
return key;
}

Expand All @@ -73,7 +82,6 @@ class AttestationManager {
this.keyServerUrl = "https://api.metaanchor.io/api/v1/attestation/keys/v4"
}


public async decode(token: string, params?: DecodeParameters): Promise<Attestation> {
try {
const parsedToken = JSON.parse(Buffer.from(token.split('.')[3], 'base64').toString('utf-8'));
Expand All @@ -87,14 +95,23 @@ class AttestationManager {
//const rawPayload = (await V4.verify(token, pubKey!, { ignoreExp: true, complete: true }))?.payload as AttestationTokenPayload;

const validatedPayload = await V4.verify(token, pubKey!, { ignoreExp: params?.ignoreExpiry, complete: true });
const attestation = new Attestation(validatedPayload?.payload as AttestationPayload);
const attestation = new Attestation(validatedPayload?.payload as AttestationPayload, token);
const hash = attestation.getHash();

if(this.keyCache.get<string>(hash)) {
const err = new Error("Attestation-Token already redeemed");
throw err;
}

if(!params?.noRedeem) {
this.keyCache.set(hash, hash);
}

return attestation;
} catch (error) {
// Package into a typed error, which will have some more developer-friendly details
throw new AttestationError(error as Error);
}

}
}

Expand Down
18 changes: 16 additions & 2 deletions tests/decoding.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ describe('Decoding tests', () => {
// Should have .errorCode() with UKNOWN_KEY, EXPIRED, ALREADY_REDEEMED, DECODE_FAILURE, KEYSERVER_UNREACHABLE etc etc.
});

it('Decodes expired token, when expiry is ignroed', async () => {
it('Decodes expired token, when expiry is ignored', async () => {
const attestation = await mgr.decode(testTokenExpired, {ignoreExpiry:true});
expect(attestation?.getSlid()).toEqual("Z45JBJR6S9");
});
Expand Down Expand Up @@ -59,5 +59,19 @@ describe('Basic usage', () => {
expect(attestation?.isAuthenticated()).toEqual(false);
expect(attestation?.isFraud()).toEqual(true);
expect(attestation?.getSlid()).toEqual("Z45JBJR6S9");
});
});

it('Tokens can only be decoded once', async () => {
const attestation = await mgr.decode(testTokenAuthentic);
await expect(mgr.decode(testTokenAuthentic, {noRedeem: true})).rejects.toThrow(AttestationError);
await expect(mgr.decode(testTokenAuthentic)).rejects.toThrow(AttestationError);
});

it('Tokens can be decoded multiple times with flag', async () => {
const attestation = await mgr.decode(testTokenAuthentic, {noRedeem: true});
expect(attestation?.getSlid()).toEqual("Z45JBJR6S9");

const attestationRedecoded = await mgr.decode(testTokenAuthentic, {noRedeem: true});
expect(attestationRedecoded?.getSlid()).toEqual("Z45JBJR6S9");
});
});

0 comments on commit e48df78

Please sign in to comment.