slackapi / deno-slack-sdk

SDK for building Run on Slack apps using Deno
https://api.slack.com/automation
MIT License
162 stars 27 forks source link

[FEATURE] Supports OAuth2 provider which does not supply identity endpoint #306

Closed koyama-tagbangers closed 4 months ago

koyama-tagbangers commented 5 months ago

Description of the problem being solved

Thanks to the Slack platform for its great OAuth2 integration.

However, there is an issue where some OAuth2 providers do not provide an endpoint to retrieve an authenticated user.

One example is Tempo.io, which can get access tokens but cannot get authenticated user. https://community.developer.atlassian.com/t/oauth2-provider-action-retrieveprofile-with-tempo-oauth/55371

I'd like to have the option to only keep one token per end user without retrieving the authenticated user.

Alternative solutions

For the OAuth flow to succeed, specify dummy API that returns fixed JSON value in the options OAuth2ProviderOptions#identity_config.

const TempoProvider = DefineOAuth2Provider({
  provider_key: "tempo",
  provider_type: Schema.providers.oauth2.CUSTOM,
  options: {
    provider_name: "Tempo",
    ...
    identity_config: {
      // Always returns {"value": "dummy"}
      url: "https://my-mock-server.app",
      // Always "dummy" value
      account_identifier: "$.value",
    },
  },
});

According to the Slack Platform documentation, obtained authenticated user via identity_config configuration is used for user selecting.

https://api.slack.com/automation/external-auth#identity-properties

If the same user has two different accounts for the the same provider, but the identity_config is configured such that the same external_user_id value (obtained via the identity_config property) is returned in case of both the accounts, the user will not be able to issue multiple tokens for multiple accounts as existing tokens will be overwritten if the external user IDs are the same. This identity is what is used to identify different accounts for a user for the same provider, and only one token per identity is stored for a user, team, and provider combination.

My understanding of this is that even if value of identity_config.account_identifier is a fixed, a separate token is stored for each end user that executes the workflow.

WilliamBergamin commented 5 months ago

Hi @koyama-tagbangers thanks for writing in 💯

Unfortunately account_identifier is a required field for slacks API, it is used to map slack accounts to externally provided accounts

For Tempo.io have you tried using the myself API as mentioned in their docs?

koyama-tagbangers commented 5 months ago

@WilliamBergamin Thanks for quick reply.

For Tempo.io have you tried using the myself API as mentioned in their docs?

myself API is the Atlassian resource, so it cannot be fetched using Tempo access token (It can be fetched with Atlassian access token). https://community.developer.atlassian.com/t/oauth2-provider-action-retrieveprofile-with-tempo-oauth/55371/5 I have been checked that I can't actually obtain it, so there is no way to obtain an authenticated user unfortunately.


My suggestion is to be able to customize how the Slack API retrieves authenticated external user, like following optional property.

DefineOAuth2Provider({
  ...
  options: {
    ...
    identity_config: {
      load_user: ({ token }) => {
         // Custom process with token, then return user value
      }
    },
  },
});
// By fetching userinfo API (maybe default behavior)
load_user: ({ token }) => {
  const response = await fetch("https://example.com/userinfo", {
    headers: {
      "Content-Type": "application/json",
      "Authorize": `${token.token_type} ${token.access_token}`,
    }
  });
  const userinfo = await response.json();
  return userinfo.email
}
// By extracting JWT token
load_user: ({ token }) => {
  const decodedToken = jwtDecode(token.access_token)
  return decodedToken.email
}
// Returning static value (The use case of this issue)
load_user: ({ token }) => {
  return "dummy" // or use slack workflow end user name/email by using text like "{{end_user}}"
}

Sorry for the minor request. I will use the workaround described in Alternative solutions if the provider support is difficult.

WilliamBergamin commented 5 months ago

I've been querying internally on this subject, I don't have an update yet, but it does seem like Tempo.io is the first use case for this

It does seem like Tempo.io could benefit from exposing a myself API type of endpoint, I would recommend reaching out to them as well in order to expose an endpoint of this nature

koyama-tagbangers commented 5 months ago

@WilliamBergamin I appreciate it. I'll also contact with the technical teams of Tempo.io to try to solve.

On the other hand, is it correct to assume from the Slack Platform documentation that even if the authenticated user value is fixed (like dummy), a separate token is stored for each end-user who invokes the workflow? (This use case assumes one Tempo.io account per end user) https://api.slack.com/automation/external-auth#identity-properties

If the same user has two different accounts for the the same provider, but the identity_config is configured such that the same external_user_id value (obtained via the identity_config property) is returned in case of both the accounts, the user will not be able to issue multiple tokens for multiple accounts as existing tokens will be overwritten if the external user IDs are the same. This identity is what is used to identify different accounts for a user for the same provider, and only one token per identity is stored for a user, team, and provider combination.

WilliamBergamin commented 5 months ago

Hi @koyama-tagbangers I have an update on this

I appear that you would be able to provide a "dummy" endpoint for the identity_config, ideally the value returned by the "dummy" endpoint should be unique for each user

The major drawback of this approach is that a user can only have one Tempo.io token saved per slack account , for example Slack would not be able to distinguish the tokens minted for a slack user that wishes to use a Tempo.io company account and a personal account.

koyama-tagbangers commented 5 months ago

@WilliamBergamin Thanks.

ideally the value returned by the "dummy" endpoint should be unique for each user

Does this mean, for example, set dummy REST API URL which returns {"id": "<random-uuid>"} to identity_config.url?

What are the effect of the returned value is fixed? I recognize that a separate token is stored for each Slack account.

Also, could you provide a way to set a fixed value on the client without fetching to the dummy endpoint?

I contacted to Tempo.io developper via Slack community. https://tempo-ecosystem.slack.com/archives/C51HZLA12/p1713837078895969 In conclusion, Tempo.io REST API does not support endpoint of authorized user info 😢

WilliamBergamin commented 5 months ago

set dummy REST API URL which returns {"id": ""} to identity_config.url?

Yes this should work, but I have not tested it out yet

What are the effect of the returned value is fixed?

Slack users will only be able to be logged in to 1 Tempo.io account at a time, for example during your development process Slack will not be able to store a Dev token and a Prod token simultaneously on behalf of your Slack Account

Could you provide a way to set a fixed value on the client without fetching to the dummy endpoint?

We are in the process of potentially adding a way to opt out of identity_config at the cost of only being able to store one token per slack account, I do not have an ETA on this feature

koyama-tagbangers commented 4 months ago

@WilliamBergamin Thank you for your politeness!

As I mentioned before, in my use case, there is only one Tempo.io account per slack user, so I don't think there will be any major problems.

Also thank you for considering my proposal, I look forward to seeing it implemented in the future.