UNPKG

@stacks/auth

Version:

Authentication for Stacks apps.

130 lines 4.29 kB
import { isSameOriginAbsoluteUrl } from '@stacks/common'; import { publicKeyToBtcAddress } from '@stacks/encryption'; import { decodeToken, TokenVerifier } from 'jsontokens'; import { getAddressFromDID } from './dids'; import { fetchAppManifest } from './provider'; export function doSignaturesMatchPublicKeys(token) { const payload = decodeToken(token).payload; if (typeof payload === 'string') { throw new Error('Unexpected token payload type of string'); } const publicKeys = payload.public_keys; if (publicKeys.length === 1) { const publicKey = publicKeys[0]; try { const tokenVerifier = new TokenVerifier('ES256k', publicKey); return tokenVerifier.verify(token); } catch (e) { return false; } } else { throw new Error('Multiple public keys are not supported'); } } export function doPublicKeysMatchIssuer(token) { const payload = decodeToken(token).payload; if (typeof payload === 'string') { throw new Error('Unexpected token payload type of string'); } const publicKeys = payload.public_keys; const addressFromIssuer = getAddressFromDID(payload.iss); if (publicKeys.length === 1) { const addressFromPublicKeys = publicKeyToBtcAddress(publicKeys[0]); if (addressFromPublicKeys === addressFromIssuer) { return true; } } else { throw new Error('Multiple public keys are not supported'); } return false; } export function isIssuanceDateValid(token) { const payload = decodeToken(token).payload; if (typeof payload === 'string') { throw new Error('Unexpected token payload type of string'); } if (payload.iat) { if (typeof payload.iat !== 'number') { return false; } const issuedAt = new Date(payload.iat * 1000); if (new Date().getTime() < issuedAt.getTime()) { return false; } else { return true; } } else { return true; } } export function isExpirationDateValid(token) { const payload = decodeToken(token).payload; if (typeof payload === 'string') { throw new Error('Unexpected token payload type of string'); } if (payload.exp) { if (typeof payload.exp !== 'number') { return false; } const expiresAt = new Date(payload.exp * 1000); if (new Date().getTime() > expiresAt.getTime()) { return false; } else { return true; } } else { return true; } } export function isManifestUriValid(token) { const payload = decodeToken(token).payload; if (typeof payload === 'string') { throw new Error('Unexpected token payload type of string'); } return isSameOriginAbsoluteUrl(payload.domain_name, payload.manifest_uri); } export function isRedirectUriValid(token) { const payload = decodeToken(token).payload; if (typeof payload === 'string') { throw new Error('Unexpected token payload type of string'); } return isSameOriginAbsoluteUrl(payload.domain_name, payload.redirect_uri); } export async function verifyAuthRequest(token) { if (decodeToken(token).header.alg === 'none') { throw new Error('Token must be signed in order to be verified'); } const values = await Promise.all([ isExpirationDateValid(token), isIssuanceDateValid(token), doSignaturesMatchPublicKeys(token), doPublicKeysMatchIssuer(token), isManifestUriValid(token), isRedirectUriValid(token), ]); return values.every(val => val); } export async function verifyAuthRequestAndLoadManifest(token) { const valid = await verifyAuthRequest(token); if (!valid) { throw new Error('Token is an invalid auth request'); } return fetchAppManifest(token); } export async function verifyAuthResponse(token) { const conditions = await Promise.all([ isExpirationDateValid(token), isIssuanceDateValid(token), doSignaturesMatchPublicKeys(token), doPublicKeysMatchIssuer(token), ]); return conditions.every(val => val); } //# sourceMappingURL=verification.js.map