import { decodeToken , TokenVerifier } from 'jsontokens' ; import { getAddressFromDID } from './dids' ; import { publicKeyToAddress } from '@stacks/encryption' ; import { fetchPrivate , isSameOriginAbsoluteUrl } from '@stacks/common' ; import { fetchAppManifest } from './provider' ; import { c32ToB58 } from 'c32check' ; param { String } token return { Boolean } throws private ignore 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 as string [ ] ; 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' ) ; } } param { String } token return { Boolean } throws private ignore 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 as string [ ] ; const addressFromIssuer = getAddressFromDID ( payload . iss ) ; if ( publicKeys . length === 1 ) { const addressFromPublicKeys = publicKeyToAddress ( publicKeys [ 0 ] ) ; if ( addressFromPublicKeys === addressFromIssuer ) { return true ; } } else { throw new Error ( 'Multiple public keys are not supported' ) ; } return false ; } param { String } token param { String } nameLookupURL return { Promise < Boolean > } private ignore export async function doPublicKeysMatchUsername ( token , nameLookupURL ) { try { const payload = decodeToken ( token ) . payload ; if ( typeof payload === 'string' ) { throw new Error ( 'Unexpected token payload type of string' ) ; } if ( ! payload . username ) { return true ; } if ( payload . username === null ) { return true ; } if ( nameLookupURL === null ) { return false ; } const username = payload . username ; const url = `${ nameLookupURL . replace ( /\/$/ , '' ) }/${ username }` ; const response = await fetchPrivate ( url ) ; const responseText = await response . text ( ) ; const responseJSON = JSON . parse ( responseText ) ; if ( responseJSON . hasOwnProperty ( 'address' ) ) { const nameOwningAddress = responseJSON . address ; let nameOwningAddressBtc = nameOwningAddress ; try { nameOwningAddressBtc = c32ToB58 ( nameOwningAddress , 0 ) ; } catch { } const addressFromIssuer = getAddressFromDID ( payload . iss ) ; if ( nameOwningAddressBtc === addressFromIssuer ) { return true ; } else { return false ; } } else { return false ; } } catch ( error ) { console . log ( error ) ; console . log ( 'Error checking `doPublicKeysMatchUsername`' ) ; return false ; } } param { String } token return { Boolean } private ignore 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 ; } } param { String } token return { Boolean } private ignore 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 ; } } param { String } token return { Boolean } private ignore 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 as string , payload . manifest_uri as string ) ; } param { String } token return { Boolean } private ignore 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 as string , payload . redirect_uri as string ) ; } param { String } token return { Promise } private ignore 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 ) ; } param { String } token return { Promise } private ignore export async function verifyAuthRequestAndLoadManifest ( token ) { const valid = await verifyAuthRequest ( token ) ; if ( ! valid ) { throw new Error ( 'Token is an invalid auth request' ) ; } return fetchAppManifest ( token ) ; } param { String } token param { String } nameLookupURL param fallbackLookupURLs return { Promise } private ignore export async function verifyAuthResponse ( token , nameLookupURL , fallbackLookupURLs ? ) { const values = await Promise . all ( [ isExpirationDateValid ( token ) , isIssuanceDateValid ( token ) , doSignaturesMatchPublicKeys ( token ) , doPublicKeysMatchIssuer ( token ) , ] ) ; const usernameMatchings = await Promise . all ( [ nameLookupURL ] . concat ( fallbackLookupURLs || [ ] ) . map ( url => doPublicKeysMatchUsername ( token , url ) ) ) ; const someUsernameMatches = usernameMatchings . includes ( true ) ; return ! ! someUsernameMatches && values . every ( val => val ) ; }