@stacks/auth
Version:
Authentication for Stacks apps.
130 lines • 4.29 kB
JavaScript
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