Socket
Socket
Sign inDemoInstall

twitch-auth

Package Overview
Dependencies
1
Maintainers
1
Versions
79
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

twitch-auth


Version published
Weekly downloads
582
decreased by-3.64%
Maintainers
1
Created
Weekly downloads
 

Readme

Source

twitch-auth

Utility module for handling Twitch.tv chatbot authentication. The authentication process is detailed here.

exports

The following functions are exported by the module.

getUserAccessToken(creds, code)

Gets a user access token required for login (using tmi.js for example). This should be used once after the initial authorization step. Subsequent logins should use the token refresh process.

creds is an object structured as follows:

{
"client_id": <your client ID>,
"client_secret": <your client secret>
}

code is the OAuth 2.0 authorization code returned by Twitch.

Returns a Promise that resolves to an object parsed from the JSON returned from the server, or rejects with an error. The returned object looks like this:

{
  "access_token": "<user access token>",
  "refresh_token": "<refresh token>",
  "expires_in": <number of seconds until the token expires>,
  "scope": "<your previously listed scope(s)>",
  "token_type": "bearer"
}

refreshUserAccessToken(creds, refreshToken)

Gets a new (refreshed) user access token for login.

creds ...same as above.

refreshToken is the refresh_token returned from the server in calls to getUserAccessToken() and/or refreshUserAccessToken().

getAppAccessToken(creds)

Gets an app access token. This is not applicable to chat login, but I wrote the function before I realized that :P

creds ...same as above.

example code

This is basically what I use to launch my chat bot. It is derived from Twitch's echo/haiku sample and has been built up from there (with the twitch-auth module being refactored out of it). It uses a local Redis server to cache access/refresh tokens. Run it initially with -c <auth code> to get your first user access token. After that, it will automatically refresh tokens as necessary.

#!/usr/bin/nodejs --harmony
//----------------------------------------------------------------

'use strict';
//----------------------------------------------------------------

const process = require('process')
const minimist = require('minimist')
const fs = require('fs')
const tmi = require('tmi.js')
const redis = require('redis');
const util = require('util')

// Twitch Auth Util (tau)
const tau = require('twitch-auth')

const defaultConfigPath = 'config.json';
const defaultConfig = {
  credsPath: './creds.json',
  reconnect: true
};
//----------------------------------------------------------------

// Called every time a message comes in:
function onMessageHandler(target, context, msg, self) {
  // Ignore messages from the bot
  if (self) {
    return;
  }
  
  console.log(msg);
}

function parseArgs(argv) {
  let args = minimist(argv.slice(2));
  if (args['_'].length > 0) {
    args._.forEach((unknownOption) => {
      console.log(`I don't know what "${unknownOption}" is.`);
    });
    
    return (null);
  }
  
  return (args);
}

function getConfigPath(args, defaultPath=defaultConfigPath) {
  if ('F' in args) {
    return (args['F']);
  }
  
  return (defaultPath);
}

function getConfig(args) {
  let configPath = getConfigPath(args);

  let config = defaultConfig;
  try {
    fs.accessSync(configPath, fs.constants.F_OK | fs.constants.R_OK);
    let configString = fs.readFileSync(configPath, 'utf8');
    config = Object.assign(config, JSON.parse(configString));
  }
  catch (err) {
    console.log(err);
  }
  
  return (config);
}

function getCreds(credsPath) {
  let credString = fs.readFileSync(credsPath, 'utf8');
  return (JSON.parse(credString));
}

function redisValue(redisClient, key) {
  return (new Promise((resolve, reject) => {
    redisClient.get(key, (err, result) => {
      console.log('redis client returned: ' + result);
      if (err) {
        reject(err);
      }
      
      resolve(result);
    });
  }));
}

function accessTokenFromAuthCode(creds, code) {
  return (tau.getUserAccessToken(creds, code));
}

function accessTokenFromCache() {
  //console.log('accessTokenFromCache()');
  
  let redisClient = redis.createClient();
  let tokens = Promise.all([
    redisValue(redisClient, "access_token"),
    redisValue(redisClient, "refresh_token")])
  .then((results) => {
    //console.log('results: ' + results);
    redisClient.end();
    return ({access_token: results[0],
             refresh_token: results[1]});
  });

  return (tokens);
}

function cacheTokenResponse(response) {
  let redisClient = redis.createClient();
  return (new Promise((resolve, reject) => {
    if (('access_token' in response ) &&
        ('refresh_token' in response)) {
      redisClient.mset('access_token', response.access_token,
                       'refresh_token', response.refresh_token, (err, reply) => {
        console.log('redis client returned: ' + reply);
        
        redisClient.end();
        if (err) {
          reject(err);
        }
        
        resolve(reply);
      });
    }
  }));
}

function reconnect(context) {
  return (tau.refreshUserAccessToken(context.creds, context.tokens.refresh_token)
          .then((response) => {
            // Update the tokens in our context oject.
            context.tokens.access_token = response.access_token;
            context.tokens.refresh_token = response.refresh_token;
            return (response);})
          .then(cacheTokenResponse)
          .then((reply) => {
            // reply is useless ("OK")
            console.log("let's try to reconnect...");
            return (context);})
          .then(serve));
}

function getTwitchOptions(context) {
  let opts = {
    options: {
      clientId: context.creds.client_id,
      debug: true
    },
    identity: {
      username: context.config.tmi.username,
      password: 'oauth:' + context.tokens.access_token
    },
    channels: context.config.tmi.channels
  }
  
  return (opts);
}

function registerTwitchEventHandlers(client, context) {
  // Register our event handlers:
  client.on('message', onMessageHandler);
  client.on('disconnected', (reason) => {
    // Login authentication failed
    console.log(util.format('Disconnected: %s', reason));
    
    reconnect(context)
    .catch((err) => {
      console.log(err);
    });
  });
}

function serve(context) {
  // Create a client with our tmi options:
  let opts = getTwitchOptions(context);
  let client = new tmi.client(opts);
  
  // Connect to Twitch:
  return (client.connect()
          .then((data) => {
            console.log(JSON.stringify(data));
            
            registerTwitchEventHandlers(client, context);
          }, (err) => {
            // Reconnect is almost always necessary because auth tokens need to be refreshed often.
            console.log("It's okay, we probably just need to refresh the auth tokens.");
            
            reconnect(context)
            .catch((err) => {
              console.log('Uh oh, reconnection attempt failed. Maybe this error message will be helpful:\n' + err);
             });
          }));
}

function launch(args) {
  let config = getConfig(args);
  let creds = getCreds(config.credsPath);
  let accessToken = Promise.resolve();
  if ('c' in args) {
    let authCode = args['c'];
    accessToken = accessToken
      .then(function() {
        return (accessTokenFromAuthCode(creds, authCode));
      })
      .then((response) => {
        return (cacheTokenResponse(response));
      });
  }
  else {
    accessToken = accessToken
      .then(function() {
        return (accessTokenFromCache());
      });
  }
  
  // Inject other context.
  accessToken
  .then((tokens) => {
    let context = {
      config: config,
      creds: creds,
      tokens: tokens
    };
    
    return (context);})
  .then(serve)
  .catch((err) => {
    console.log(err);
    process.exit(1);
  });
}
//----------------------------------------------------------------

launch(parseArgs(process.argv));
//----------------------------------------------------------------

My config.json looks like this:

{
  "tmi": {
    "channels": [
      "laddspencer"
    ],
    "username": "PhantsBot"
  }
}

My creds.json looks like this (no, those are not real creds, use your own!):

{
"client_id": "4jkcd8ejjwkemvnhuewnc98ku87uyh",
"client_secret": "d9rkkijun4jfunywhqssx6456hey7u"
}

Keywords

FAQs

Last updated on 20 Sep 2018

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc