2020-07-15 18:56:08 +02:00
"use strict" ;
2016-02-04 14:03:39 -05:00
// Helper functions for accessing the google API.
var Parse = require ( 'parse/node' ) . Parse ;
2020-07-15 18:56:08 +02:00
const https = require ( 'https' ) ;
const jwt = require ( 'jsonwebtoken' ) ;
2020-07-29 20:25:59 +05:30
const TOKEN _ISSUER = 'accounts.google.com' ;
const HTTPS _TOKEN _ISSUER = 'https://accounts.google.com' ;
2020-07-15 18:56:08 +02:00
let cache = { } ;
// Retrieve Google Signin Keys (with cache control)
function getGoogleKeyByKeyId ( keyId ) {
if ( cache [ keyId ] && cache . expiresAt > new Date ( ) ) {
return cache [ keyId ] ;
}
return new Promise ( ( resolve , reject ) => {
https . get ( ` https://www.googleapis.com/oauth2/v3/certs ` , res => {
let data = '' ;
res . on ( 'data' , chunk => {
data += chunk . toString ( 'utf8' ) ;
} ) ;
res . on ( 'end' , ( ) => {
const { keys } = JSON . parse ( data ) ;
const pems = keys . reduce ( ( pems , { n : modulus , e : exposant , kid } ) => Object . assign ( pems , { [ kid ] : rsaPublicKeyToPEM ( modulus , exposant ) } ) , { } ) ;
if ( res . headers [ 'cache-control' ] ) {
var expire = res . headers [ 'cache-control' ] . match ( /max-age=([0-9]+)/ ) ;
if ( expire ) {
cache = Object . assign ( { } , pems , { expiresAt : new Date ( ( new Date ( ) ) . getTime ( ) + Number ( expire [ 1 ] ) * 1000 ) } ) ;
}
}
resolve ( pems [ keyId ] ) ;
} ) ;
} ) . on ( 'error' , reject ) ;
2018-09-01 13:58:06 -04:00
} ) ;
2016-10-17 12:44:24 -04:00
}
2020-07-15 18:56:08 +02:00
function getHeaderFromToken ( token ) {
const decodedToken = jwt . decode ( token , { complete : true } ) ;
if ( ! decodedToken ) {
throw new Parse . Error ( Parse . Error . OBJECT _NOT _FOUND , ` provided token does not decode as JWT ` ) ;
}
return decodedToken . header ;
2016-02-04 14:03:39 -05:00
}
2020-07-15 18:56:08 +02:00
async function verifyIdToken ( { id _token : token , id } , { clientId } ) {
if ( ! token ) {
throw new Parse . Error ( Parse . Error . OBJECT _NOT _FOUND , ` id token is invalid for this user. ` ) ;
}
const { kid : keyId , alg : algorithm } = getHeaderFromToken ( token ) ;
let jwtClaims ;
const googleKey = await getGoogleKeyByKeyId ( keyId ) ;
try {
jwtClaims = jwt . verify ( token , googleKey , { algorithms : algorithm , audience : clientId } ) ;
} catch ( exception ) {
const message = exception . message ;
throw new Parse . Error ( Parse . Error . OBJECT _NOT _FOUND , ` ${ message } ` ) ;
}
2020-07-29 20:25:59 +05:30
if ( jwtClaims . iss !== TOKEN _ISSUER && jwtClaims . iss !== HTTPS _TOKEN _ISSUER ) {
throw new Parse . Error ( Parse . Error . OBJECT _NOT _FOUND , ` id token not issued by correct provider - expected: ${ TOKEN _ISSUER } or ${ HTTPS _TOKEN _ISSUER } | from: ${ jwtClaims . iss } ` ) ;
2020-07-15 18:56:08 +02:00
}
if ( jwtClaims . sub !== id ) {
throw new Parse . Error ( Parse . Error . OBJECT _NOT _FOUND , ` auth data is invalid for this user. ` ) ;
2016-10-17 12:44:24 -04:00
}
2020-07-15 18:56:08 +02:00
if ( clientId && jwtClaims . aud !== clientId ) {
throw new Parse . Error ( Parse . Error . OBJECT _NOT _FOUND , ` id token not authorized for this clientId. ` ) ;
}
return jwtClaims ;
}
// Returns a promise that fulfills if this user id is valid.
function validateAuthData ( authData , options ) {
return verifyIdToken ( authData , options ) ;
2016-10-17 12:44:24 -04:00
}
// Returns a promise that fulfills if this app id is valid.
2016-02-04 14:03:39 -05:00
function validateAppId ( ) {
return Promise . resolve ( ) ;
}
module . exports = {
validateAppId : validateAppId ,
2020-07-15 18:56:08 +02:00
validateAuthData : validateAuthData
2016-02-04 14:03:39 -05:00
} ;
2020-07-15 18:56:08 +02:00
// Helpers functions to convert the RSA certs to PEM (from jwks-rsa)
function rsaPublicKeyToPEM ( modulusB64 , exponentB64 ) {
const modulus = new Buffer ( modulusB64 , 'base64' ) ;
const exponent = new Buffer ( exponentB64 , 'base64' ) ;
const modulusHex = prepadSigned ( modulus . toString ( 'hex' ) ) ;
const exponentHex = prepadSigned ( exponent . toString ( 'hex' ) ) ;
const modlen = modulusHex . length / 2 ;
const explen = exponentHex . length / 2 ;
const encodedModlen = encodeLengthHex ( modlen ) ;
const encodedExplen = encodeLengthHex ( explen ) ;
const encodedPubkey = '30' +
encodeLengthHex ( modlen + explen + encodedModlen . length / 2 + encodedExplen . length / 2 + 2 ) +
'02' + encodedModlen + modulusHex +
'02' + encodedExplen + exponentHex ;
const der = new Buffer ( encodedPubkey , 'hex' )
. toString ( 'base64' ) ;
let pem = '-----BEGIN RSA PUBLIC KEY-----\n' ;
pem += ` ${ der . match ( /.{1,64}/g ) . join ( '\n' ) } ` ;
pem += '\n-----END RSA PUBLIC KEY-----\n' ;
return pem ;
}
function prepadSigned ( hexStr ) {
const msb = hexStr [ 0 ] ;
if ( msb < '0' || msb > '7' ) {
return ` 00 ${ hexStr } ` ;
}
return hexStr ;
}
function toHex ( number ) {
const nstr = number . toString ( 16 ) ;
if ( nstr . length % 2 ) {
return ` 0 ${ nstr } ` ;
}
return nstr ;
}
function encodeLengthHex ( n ) {
if ( n <= 127 ) {
return toHex ( n ) ;
}
const nHex = toHex ( n ) ;
const lengthOfLengthByte = 128 + nHex . length / 2 ;
return toHex ( lengthOfLengthByte ) + nHex ;
}