Google Home & Typescript

I’m trying to integrate Auth0 with the sample (https://github.com/actions-on-google/smart-home-nodejs but I’m not sure if my code is right and its not working. I have tried the code snippet from Auth0 community (Auth0 Integration with GoogleHome) but I don’t get it.
Here is my auth-provider.ts

import { Headers } from 'actions-on-google'
import { AuthenticationClient } from 'auth0'
import * as Firestore from './firestore'

const auth0 = new AuthenticationClient({
    domain: 'localsenzo2.auth0.com',
    clientId: 'secret',
    clientSecret: 'secret'
});


/**
 * A function that gets the user id from an access token.
 * Replace this functionality with your own OAuth provider.
 *
 * @param headers HTTP request headers
 * @return The user id
 */
export async function getUser(headers: Headers): Promise<string> {
    const authorization = headers.authorization
    const accessToken = (authorization as string).substr(7)
    const { email } = await auth0.getProfile(accessToken)
    console.log(email)
    return await Firestore.getUserId(accessToken)
}

Hi @46deva,

since that’s just part of the whole code, namely the part where you fetch the user info with an access token that you must have gotten beforehand:

What exactly is the error or issue? Are you getting an access token successfully but just the auth0.getProfile() is failing, or are you already not getting an access token?

Is there any error in either Auth0 log or in your app-side console?

Hey, yes. Sorry. Let me upload the whole code. For your information, index.ts is ran on ngrok express server that acts as my smart home server which receives intents from Google. firestore.ts communicates with Firestore for a users’ devices to control them or read the current state. auth-provider.ts is where the authentication is. I need my smart home server receive the onSync intent from Google after getting authenticated. Now the issue is the server is not receiving any request or I am not sure how to know if it received any token.

index.ts

// Express imports
import * as express from 'express'
import * as bodyParser from 'body-parser'
import * as cors from 'cors'
import * as morgan from 'morgan'
import * as ngrok from 'ngrok'
import { AddressInfo } from 'net'

// Smart home imports
import {
    smarthome,
    SmartHomeV1ExecuteResponseCommands,
    Headers,
} from 'actions-on-google'

// Local imports
import * as Firestore from './firestore'
import * as Auth from './auth-provider'
import * as Config from './config-provider'

const expressApp = express()
expressApp.use(cors())
expressApp.use(morgan('dev'))
expressApp.use(bodyParser.json())
expressApp.use(bodyParser.urlencoded({ extended: true }))
expressApp.set('trust proxy', 1)

//Auth.registerAuthEndpoints(expressApp)
Auth.getUser

let jwt
try {
    jwt = require('./smart-home-key.json')
} catch (e) {
    console.warn('Service account key is not found')
    console.warn('Report state and Request sync will be unavailable')
}

const app = smarthome({
    jwt,
    debug: true,
})

// Array could be of any type
// tslint:disable-next-line
async function asyncForEach(array: any[], callback: Function) {
    for (let index = 0; index < array.length; index++) {
        await callback(array[index], index, array)
    }
}

async function getUserIdOrThrow(headers: Headers): Promise<string> {
    const userId = await Auth.getUser(headers)
    const userExists = await Firestore.userExists(userId)
    if (!userExists) {
        throw new Error(`User ${userId} has not created an account, so there are no devices`)
    }
    return userId
}

app.onSync(async (body, headers) => {
    const userId = await getUserIdOrThrow(headers)
    await Firestore.setHomegraphEnable(userId, true)

    const devices = await Firestore.getDevices(userId)
    return {
        requestId: body.requestId,
        payload: {
            agentUserId: userId,
            devices,
        },
    }
})

firestore.ts

import * as admin from 'firebase-admin'
import { SmartHomeV1SyncDevices, SmartHomeV1ExecuteRequestExecution } from 'actions-on-google'
import { ApiClientObjectMap } from 'actions-on-google/dist/common'
import {googleCloudProjectId} from './config-provider'
const serviceAccount = require('./firebase-admin-key.json');
admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
  databaseURL: `https://${googleCloudProjectId}.firebaseio.com`,
})
const db = admin.firestore()
const settings = {timestampsInSnapshots: true}
db.settings(settings)

export async function userExists(userId: string): Promise<boolean> {
  const userDoc = await db.collection('users').doc(userId).get()
  return userDoc.exists
}

export async function getUserId(accessToken: string): Promise<string> {
  const querySnapshot = await db.collection('users')
      .where('fakeAccessToken', '==', accessToken).get()
  if (querySnapshot.empty) {
    throw new Error('No user found for this access token')
  }
  const doc = querySnapshot.docs[0]
  return doc.id // This is the user id in Firestore
}

export async function homegraphEnabled(userId: string): Promise<boolean> {
  const userDoc = await db.collection('users').doc(userId).get()
  return userDoc.data()!!.homegraph
}

export async function setHomegraphEnable(userId: string, enable: boolean) {
  await db.collection('users').doc(userId).update({
    homegraph: enable,
  })
}

auth-provider.ts

import { Headers } from 'actions-on-google'
import { AuthenticationClient } from 'auth0'
import * as Firestore from './firestore'


const auth0 = new AuthenticationClient({
    domain: 'localsenzo2.auth0.com',
    clientId: 'secret',
    clientSecret: 'secret'
});


/**
 * A function that gets the user id from an access token.
 * Replace this functionality with your own OAuth provider.
 *
 * @param headers HTTP request headers
 * @return The user id
 */
export async function getUser(headers: Headers): Promise<string> {
    const authorization = headers.authorization
    const accessToken = (authorization as string).substr(7)
    const { email } = await auth0.getProfile(accessToken)
    console.log(email)
    return await Firestore.getUserId(accessToken)
}

Hi @46deva,

I don’t have full knowledge on the subject to help you, but the tutorial helped me a lot: Authenticating smart home Actions for the Google Assistant with Auth0 by Nick Felker and the original code: https://github.com/actions-on-google/smart-home-nodejs.

Below is my code that works:

index.ts


// Express imports
import * as express from 'express'
import * as bodyParser from 'body-parser'
import * as cors from 'cors'
import * as morgan from 'morgan'
import * as ngrok from 'ngrok'
import { AddressInfo } from 'net'

// Smart home imports
import {
  smarthome,
  SmartHomeV1ExecuteResponseCommands,
  Headers,
} from 'actions-on-google'

// Local imports
import * as Firestore from './firestore'
import * as Auth from './auth-provider'
import * as Config from './config-provider'

const expressApp = express()
expressApp.use(cors())
expressApp.use(morgan('dev'))
expressApp.use(bodyParser.json())
expressApp.use(bodyParser.urlencoded({extended: true}))
expressApp.set('trust proxy', 1)

Auth.registerAuthEndpoints(expressApp)

let jwt
try {
  jwt = require('./smart-home-key.json')
} catch (e) {
  console.warn('Service account key is not found')
  console.warn('Report state and Request sync will be unavailable')
}

const app = smarthome({
  jwt,
  debug: true,
})

// Array could be of any type
// tslint:disable-next-line
async function asyncForEach(array: any[], callback: Function) {
  for (let index = 0; index < array.length; index++) {
    await callback(array[index], index, array)
  }
}

async function getUserIdOrThrow(headers: Headers): Promise {
  var userId = await Auth.getUser(headers)
  if (!userId) {
    console.log('>>>> getUserIdOrThrow deu Ruim ')
    userId = await Auth.getUser(headers)
  }
  console.log('>>>> getUserIdOrThrow : ', userId)
  const userExists = await Firestore.userExists(userId)
  if (!userExists) {
    throw new Error(`User ${userId} has not created an account, so there are no devices`)
  }
  return userId
}

app.onSync(async (body, headers) => {
  const userId = await getUserIdOrThrow(headers)
  await Firestore.setHomegraphEnable(userId, true)

  const devices = await Firestore.getDevices(userId)
  return {
    requestId: body.requestId,
    payload: {
      agentUserId: userId,
      devices,
    },
  }
})

interface DeviceStatesMap {
  // tslint:disable-next-line
  [key: string]: any
}
app.onQuery(async (body, headers) => {
  const userId = await getUserIdOrThrow(headers)
  const deviceStates: DeviceStatesMap = {}
  const {devices} = body.inputs[0].payload
  await asyncForEach(devices, async (device: {id: string}) => {
    const states = await Firestore.getState(userId, device.id)
    deviceStates[device.id] = states
  })


  return {
    requestId: body.requestId,
    payload: {
      devices: deviceStates,
    },
  }
})

app.onExecute(async (body, headers) => {
  const userId = await getUserIdOrThrow(headers)
  const commands: SmartHomeV1ExecuteResponseCommands[] = [{
    ids: [],
    status: 'SUCCESS',
    states: {},
  }]
  //console.log('>>>> userId <<< {
    try {
      const states = await Firestore.execute(userId, device.id, execution[0])
      commands[0].ids.push(device.id)
      commands[0].states = states

     
      await app.reportState({
        agentUserId: userId,
        //requestId: Math.random().toString(),
        requestId: body.requestId,
        payload: {
          devices: {
            states: {
              [device.id]: states,
            },
          },
        },
      })
     
  return {
    requestId: body.requestId,
    payload: {
      commands,
    },
  }
})

app.onDisconnect(async (body, headers) => {
  const userId = await getUserIdOrThrow(headers)
  await Firestore.disconnect(userId)
})

expressApp.post('/smarthome', app)

expressApp.post('/smarthome/update', async (req, res) => {
  console.log(req.body)
  const {userId, deviceId, name, nickname, states} = req.body
  try {
    await Firestore.updateDevice(userId, deviceId, name, nickname, states)
    const reportStateResponse = await app.reportState({
      agentUserId: userId,
      requestId: Math.random().toString(),
      payload: {
        devices: {
          states: {
            [deviceId]: states,
          },
        },
      },
    })

    res.status(200).send('OK')
  } catch(e) {
    console.error(e)
    res.status(400).send(`Error reporting state: ${e}`)
  }
})

expressApp.post('/smarthome/create', async (req, res) => {
  console.log(req.body)
  const {userId, data} = req.body
  try {
    await Firestore.addDevice(userId, data)
    await app.requestSync(userId)
  } catch(e) {
    console.error(e)
  } finally {
    res.status(200).send('OK')
  }
})

expressApp.post('/smarthome/delete', async (req, res) => {
  console.log(req.body)
  const {userId, deviceId} = req.body
  try {
    await Firestore.deleteDevice(userId, deviceId)
    await app.requestSync(userId)
  } catch(e) {
    console.error(e)
  } finally {
    res.status(200).send('OK')
  }
})

const appPort = process.env.PORT || Config.expressPort

const expressServer = expressApp.listen(appPort, () => {
  const server = expressServer.address() as AddressInfo
  const {address, port} = server

  console.log(`Smart home server listening at ${address}:${port}`)

  if (Config.useNgrok) {
    ngrok.connect(Config.expressPort, (err, url) => {
      if (err) {
        console.error('Ngrok was unable to start')
        console.error(err)
        process.exit()
      }

      console.log('')
      console.log('COPY & PASTE NGROK URL BELOW')
      console.log(url)
      console.log('')
      console.log('=====')
      console.log('Visit the Actions on Google console at http://console.actions.google.com')
      console.log('Replace the webhook URL in the Actions section with:')
      console.log('    ' + url + '/smarthome')

      console.log('')
      console.log('In the console, set the Authorization URL to:')
      console.log('    ' + url + '/fakeauth')

      console.log('')
      console.log('Then set the Token URL to:')
      console.log('    ' + url + '/faketoken')
      console.log('')

      console.log('Finally press the \'TEST DRAFT\' button')
    })
  }
})

auth-provider.ts


import * as express from 'express'
import * as util from 'util'
import { Headers } from 'actions-on-google'
import * as Firestore from './firestore'
import { AuthenticationClient } from 'auth0'

const auth0 = new AuthenticationClient({
  domain: my-domain,
  clientId: my-clientID,
  clientSecret: my-clienteSecret
});

export async function getUser(headers: Headers): Promise {
  const authorization = headers.authorization
  const accessToken = (authorization as string).substr(7)
  const {email} = await auth0.getProfile(accessToken)
  
  const userExists = await Firestore.userExists(email)
  if (!userExists) {
    await Firestore.addUser(email, email)
  }
  return await Firestore.getUserIdByEmail(email)
}