Mixin example

Mixin example: Save tokens in Google Secret Store

This example assumes you have gcloud tools installed on your machine and the secretmanager.googleapis.com api enabled for the project you are working in. When testing this code on your local machine you will need to setup the credentials correctly using the the bash environment variable GOOGLE_APPLICATION_CREDENTIALS. here you can find more info on how to set this up.

The ChipChat library has two special hooks, getTokens() and setTokens(), which you can overrule to provide your own way of retrieving and storing the tokens.

The example consists of four parts:

  • Creating the bot
  • The token-store-mixin.js that overrrules the getTokens and setTokens functions of ChipChat and uses the google-secret-manager to get and store them in the Google Secrets Store
  • example-usage.js that showcases its usage.

Creating the bot

But first you need to create a bot. Go to your org in Web1on1 > Bots and click create. Give it a name and a description and set the capibilities to Updating resources, then click create bot.

You will need the bot id and the bot token and refreshToken, so click on the bot to open its properties panel. You can find the Bot ID here at the top. Goto the API Token panel and click Create API Token.

Start a terminal where we will play with this mixin and export the project, token, refreshToken, botid and conversation id, so we can use it in our examples.

export GOOGLEPROJECT=<paste your Google project id where you activeated the secretmanager.googleapis.com api here>
export TOKEN=<paste your created token here>
export REFRESHTOKEN=<paste your created refreshToken here>
export BOTID=<paste your create bot id>
export CONVERSATION=<paste the conversation id that we will retrieve as an example here>

Then from that same terminal, we store the token and refreshToken using the id of the bot into the Google Secret Store:

echo -n '{"token":"'$TOKEN'","refreshToken":"'$REFRESHTOKEN'"}' | gcloud secrets create ${BOTID}_tokens \
      --data-file=- \
      --replication-policy automatic \
      --project=$GOOGLEPROJECT

Now let's have a look at the token-store-mixin.js:

token-store-mixin.js

const debug = require('debug');
const ChipChat = require('chipchat');
const { SecretManagerServiceClient } = require('@google-cloud/secret-manager');

const client = new SecretManagerServiceClient();
const log = debug('google-secrets: store:');
const storename = `projects/${process.env.GOOGLEPROJECT}/secrets`;

const cache = {};

async function getTokens() {
    if (this.auth.email) {
        const tokenid = this.auth.email.split(/\+|@/)[1];
        log(`getTokens: using tokenid ${tokenid}`);
        try {
            let tokens = cache[`${tokenid}_tokens`];
            if (tokens) {
                log('tokens from cache');
            } else {
                const [secret] = await client.accessSecretVersion({ name: `${storename}/${tokenid}_tokens/versions/latest` });
                if (secret.payload && secret.payload.data) {
                    tokens = secret.payload.data.toString();
                    tokens = JSON.parse(tokens);
                    cache[`${tokenid}_token`] = tokens;
                } else {
                    tokens = {};
                }
            }
            return tokens;
        } catch (e) {
            log(`getTokens: darn, something went wrong: ${e}`);
            return {};
        }
    }
    throw new Error('token store mixin: Must provide valid email');
}

async function setTokens(tokens) {
    const tokenid = (ChipChat.decodeJwt(tokens) || {})._id;
    if (tokenid) {
        log(`setTokens: using tokenid ${tokenid}`);
        try {
            await client.addSecretVersion({
                parent: `${storename}/${tokenid}_tokens`,
                payload: {
                    data: Buffer.from(tokens, 'utf8')
                }
            });
            cache[`${tokenid}_tokens`] = tokens; // Update cache
        } catch (e) {
            log(`setTokens: darn, something went wrong: ${e}`);
        }
    } else {
        throw new Error('token store mixin: Must provide token');
    }
}

module.exports = {
    getTokens,
    setTokens
};

mixins/tokens-in-google-secrets-store/token-store-mixin.js

And finally we use the mixin in an example:

example-usage.js

/*
 * before you can use this, you have to set the <botid>_token
 * in the store from the CLI with
 * echo -n '{"token":"<paste your token here>", "refreshToken":"<paste your refreshToken here>"}' \
 * | gcloud secrets create $BOTID_token
 * --data-file=- --replication-policy automatic --project $GOOGLEPROJECT
 */

const ChipChat = require('chipchat');
const { getTokens, setTokens } = require('./token-store-mixin');

ChipChat.mixin({ getTokens, setTokens });

// The bots email is needed to request new tokens
// with the refreshToken and is also used to store the
// tokens in the google secrest store.
// The bots user id that is found in
// the properties panel of the bot (for the bot owner)
const email = `bot+${process.env.BOTID}@chatshipper.com`;
const bot = new ChipChat({ email }); // no need for tokens, email is enought

//alternatively you can use new ChipChat({ token, refreshToken })

const conversationid = process.env.CONVERSATION;
bot.conversations.get(conversationid).then(console.log);

mixins/tokens-in-google-secrets-store/example-usage.js