Module: Teems::Services::TokenExtractorScripts

Included in:
TokenExtractor, TokenPolling, TokenV2Decryptor
Defined in:
lib/teems/services/token_extractor_scripts.rb

Overview

JavaScript constants used by TokenExtractor for v1 token extraction and v2 AES-CBC decryption. Separated to keep modules under line limits.

Constant Summary collapse

EXTRACT_TOKENS_JS =

JavaScript to extract tokens from Teams web app localStorage.

<<~JS
  (function() {
    var result = { auth_token: null, skype_spaces_token: null,
                   refresh_token: null, client_id: null, tenant_id: null };

    try {
      for (var i = 0; i < localStorage.length; i++) {
        var key = localStorage.key(i);

        // Teams v1 / MSAL format: accesstoken keys with secret field
        if (key.includes('accesstoken')) {
          var value = localStorage.getItem(key);
          var parsed = JSON.parse(value);
          if (parsed.secret) {
            if (key.includes('graph.microsoft.com')) {
              result.auth_token = parsed.secret;
            }
            if (key.includes('api.spaces.skype.com')) {
              result.skype_spaces_token = parsed.secret;
            }
          }
        }

        // MSAL refresh token (always v1/unencrypted)
        if (key.includes('refreshtoken')) {
          var rtVal = localStorage.getItem(key);
          var rt = JSON.parse(rtVal);
          if (rt.secret) {
            result.refresh_token = rt.secret;
            result.client_id = rt.clientId;
            if (rt.homeAccountId && rt.homeAccountId.indexOf('.') !== -1) {
              result.tenant_id = rt.homeAccountId.split('.')[1];
            }
          }
        }
      }
    } catch(e) {}

    return JSON.stringify(result);
  })()
JS
DECRYPT_TOKENS_JS =

JavaScript to kick off async decryption of Teams v2 encrypted tokens.

<<~JS
  (function() {
    var RESULT_KEY = '{{result_key}}';
    localStorage.removeItem(RESULT_KEY);

    var keyDataRaw = localStorage.getItem('tmp.auth.v1.GLOBAL.ExportedEncryptionKey.ExportedEncryptionKey');
    if (!keyDataRaw) {
      localStorage.setItem(RESULT_KEY, JSON.stringify({error: 'no_encryption_key'}));
      return 'no_key';
    }

    var keyData = JSON.parse(keyDataRaw);
    var exportedKey = keyData.item.exportedKey;
    var keyBytes = Uint8Array.from(atob(exportedKey), function(c) { return c.charCodeAt(0); });

    function getEncItem(keyPart) {
      for (var i = 0; i < localStorage.length; i++) {
        var k = localStorage.key(i);
        if (k.includes(keyPart)) {
          var val = JSON.parse(localStorage.getItem(k));
          return val.item || val;
        }
      }
      return null;
    }

    function decryptToken(item) {
      if (!item || !item.encryptedToken || !item.iv) return Promise.resolve(null);
      var iv = Uint8Array.from(atob(item.iv), function(c) { return c.charCodeAt(0); });
      var enc = Uint8Array.from(atob(item.encryptedToken), function(c) { return c.charCodeAt(0); });
      return crypto.subtle.importKey('raw', keyBytes, {name: 'AES-CBC'}, false, ['decrypt'])
        .then(function(ck) { return crypto.subtle.decrypt({name: 'AES-CBC', iv: iv}, ck, enc); })
        .then(function(d) {
          var text = new TextDecoder().decode(d);
          var padLen = text.charCodeAt(text.length - 1);
          if (padLen > 0 && padLen <= 16) text = text.substring(0, text.length - padLen);
          return text;
        })
        .catch(function() { return null; });
    }

    var graph = getEncItem('Token.HTTPS://GRAPH.MICROSOFT.COM');
    var skype = getEncItem('Token.HTTPS://API.SPACES.SKYPE.COM');

    Promise.all([decryptToken(graph), decryptToken(skype)])
      .then(function(results) {
        var r = { auth_token: results[0], skype_spaces_token: results[1] };
        localStorage.setItem(RESULT_KEY, JSON.stringify(r));
      })
      .catch(function(e) {
        localStorage.setItem(RESULT_KEY, JSON.stringify({error: e.message}));
      });

    return 'started';
  })()
JS
READ_DECRYPT_RESULT_JS =
<<~JS
  (function() {
    return localStorage.getItem('{{result_key}}');
  })()
JS