redis / ioredis

🚀 A robust, performance-focused, and full-featured Redis client for Node.js.
MIT License
14.31k stars 1.19k forks source link

How to gracefully refresh accesstoken instead of using static passwords? #1810

Open tago-SE opened 1 year ago

tago-SE commented 1 year ago

How to work with password which expire after some time or that are not present initially?

Microsoft released an example of how to set it up using ioredis, but the problem is that I cannot get it to work as I want.

Global Redis Client

Having only one global redis client and once the access token expires you would need to ensure that it swaps the old instance with a new instance after some period of time, but would such a solution which is suggested in azure-sdk-for-js even work?

Local Redis Client

You create an async factory for providing redis clients with the latest password and ensure that the client is freed after it has been used within a small scope. No need to worry about updating the redis options to match the latest password after the access token expires. However, the issue I ran into with this solution is that it eventually ran out of connections. 'ERR max number of clients reached'. Do you have any recommendations for going this route?

// Example
  const deleteAccount = async (id: string) => {
    const client = await getRedisClientAsync();
    try {
      const key = getAccountKey(id);
      const account = (await loadObjectFromHash(key)) as AdapterAccount;
      if (!account.userId) return null;    
      await client.hdel(key);
      await client.del(getAccountByUserIdKey(account.userId));

    } finally {
      client.quit();
    }
  };

Question Is there any guidance on implementing access token based redis clients. Or how can I override the default way redis handles connections to use a username/password that is resolved asyncronously after the client has been created? It would be good if these overrides could be added to the constructor so that developers can work with access tokens without having to hack around it like I'm trying to do.

Thanks.

tago-SE commented 1 year ago

I attempted to override the connect method by fetching the password before passing it to the original connect. But the issue here is that it doesn't work if the getPassword() function is a promise. It also looks like it under the hood is doing more things than connecting and hence this wouldn't solve the main issue I'm having.

export function getRedisClient(): Redis {
  const c = createRedisInstance();
 const _clientConnect = c.connect;
  const connect = async (callback?: Callback<void>) => {
    const password = await getPassword(); // having await here does not work but writing the password directly does strangly. 
    const username = env.REDIS_USERNAME;
    debugInfo(tag, `[connect override] ${username} ${password}`);
    c.options.username = username;
    c.options.password = password;
    await _clientConnect.call(c, callback);
  };
  c.connect = connect;
  return c;
}

Suggestion: Change the password RedisOption to allow it to be either a string or a Promise, if it is a promise it should be resolved directly before auth is executed. Thus mitigating the problem im experiencing.

     /**
     * If set, client will send AUTH command with the value of this option when connected.
     */
    password?: string | Promise<string>
abustany commented 7 months ago

See also #1738