How should Slack bot tokens be stored? - slack-api

I'm building my first Slack bot and I've got the basics mostly working... sending API requests, receiving commands and events, etc. But the part I'm left a bit confused about is what I'm supposed to do with the "Bot User OAuth Access Token".
The token appears to be shared across teams/workspaces, but it is returned to be during authentication of individual users with a call to /oauth.v2.access. Currently I'm storing the returned credentials payload in a table that has three columns:
My app's internal user ID
The Slack user ID embedded in the payload as authed_user.id
The entire JSON payload itself (jsonb in postgres if you're curious)
This allows me to initiate new API calls for actions that take place in my app (find by internal user ID) and also for interactions within Slack (find by Slack user ID).
What has left me a bit puzzled is what the convention is for when a user interacts with my bot that hasn't added my app. This can happen when a person ("Jose") adds my app and then their colleague ("Mary") discovers it in Slack and views the home screen, sends it a message, etc.
In order to take some action, such as prompt for the user to install my app, I need a token. Of course I have a token for Jose but not for Mary. I also have Jose's team ID stored in my table and Mary's team ID as part of the incoming event. So technically I could do something like this to get a working token to interact with Mary:
select credential_json from slack_credentials
where credential_json->>'type' = 'bot' and credential_json->'team'->>'id' = :marysTeamId
... which would pull out the bot token I captured when Jose added the app. This works, but it feels very wrong. I suppose if I just stored bot tokens in a separate table that looked like this:
The Slack team ID embedded in the payload as team.id
A subset of the JSON payload (ex: access_token, scope, bot_user_id, etc but not authed_user)
Then it wouldn't feel so yucky. But the docs + API ergonomics don't suggest this is a common approach either. So I'm curious what others do. If I don't hear anything back, I suppose my plan is to break out the bot tokens into a team-centric table.
Thanks!

The basic concept of Slack apps is that they are installed per workspace, not per user.
So while it's true that the app's token is derived from the user who installed your app to a new workspace, most the apps function are available to all users of the workspace.
e.g. slash commands will work for every user in every channel
e.g. posts of your app will be visible to all users of the related channel.
Therefore the best approach for storing tokens usually is with a primary key of Slack Team ID, Slack User ID.
And just to clarify. You do not need a token to prompt a user to install you app. Every app can be installed from webpage hosted by you (with the "Add to Slack button") or directly from the App Directory.

Related

How to get the Slack DM channel ID of the Slack App

I have created a simple Slack App app where the only purpose is to send a message to a channel. I understand that there is the conversations.list API to list all public channels to get the correct ID.
However, as a first step, I just want to send the message to the app channel itself. If I use the D... ID it works as expected. No invite by the channel is needed. But how do I get this ID? conversations.list only returns publich channels, but no the app channel itself.
In Slack, there is no such thing as an app's channel. There is a DM channel between every user and your app/bot. In these terms, to send a DM message from your app/bot to the user, you need to know ID of this user and specify it as a channel argument of the postMessage API request.
The ability to pass a Slack user ID as channel is somewhat unique to chat.postMessage. If you try this with other API methods which expect a channel ID only (conversations.info), you'll get "error": "channel_not_found". The docs state:
Begin a conversation in a user's App Home
Start a conversation with users in your App Home.
With the chat:write scope enabled, call chat.postMessage and pass a user's ID (U0G9QF9C6) as the value of channel to post to that user's App Home channel. You can use their direct message channel ID (as found with im.open, for instance) instead.
Source: https://api.slack.com/methods/chat.postMessage#app_home
Note: The above behavior assumes you're using the bot token. If you provide the user token instead, you'll make the user DM themselves.
Now, if you *do* need to get a user's App Home channel ID for use outside of chat.postMessage, keep reading... Here are three ways to do it, each with their own shortcomings:
1. chat.postMessage
Well, it's worth mentioning that if you are going to use chat.postMessage, it returns the resolved channel ID in its response: "channel": "D01234ABCDE". You can save this for later use.
2. conversations.open
The API method im.open referenced in the docs above has been renamed to conversations.open, which can be used to obtain the user's App Home channel ID:
Use the user token, and set users to the app bot ID, or
Use the bot token, and set users to the user ID.
Though, I've observed some weirdness with conversations.open, which may or may not be a dealbreaker for you:
It requires stronger OAuth permission scopes than ones required to initiate a private DM with a user than chat.postMessage (a bot token with chat:write is insufficient), and
It behaves strangely with respect to the "open state" of a conversation. For example...
I tested this method with a user token.
The user for that token already had a DM channel with the app! (Doesn't that mean the conversation is already open?)
Strangely, the first response had is_opened: false (and subsequent responses had is_opened: true).
3. app_home_opened
The event app_home_opened fires when the user opens your App Home. If you handle this event, you can save the channel in the event payload on your server and use it later, obviating the need to later call conversations.open.
Since the event only occurs if and when the user opens your app's App Home, this approach is more of an optimization than a standalone solution.
4. A better way...?
Due to the drawbacks outlines above, if anyone knows a better way of getting the App Home channel ID for a Slack user, please, comment on this answer!
You can get the channel ID of the app channel through the GUI if you:
navigate to a message in the channel
go to the more actions men
select copy link
The channel ID will be the string following archives/ eg:
{myorg}.slack.com/archives/{channel_ID}
Using this approach would work for any channel in the slack app, though it's unlikely to be the best approach since it's manual vs something more programmatic.

Microsoft Graph API SDK .NET Issues getting other users emails

I am using the Microsoft Graph SDK as downloaded from NuGet (1.2). I authenticate to Azure AD (using ADAL).
I am using Client Credentials flow (not authenticated as any particular user) and am using Application Permission roles to access resources.
We are going to set up one service mailbox with a bunch of aliases. The aliases are given to the clients. This is so they are emailing an address that has a meaningful name to them.
My app will run as a service, and routinely scan new emails in this inbox. It should find the To address, and depending on what alias was used, file the email in a location relevant to that client.
The resource I want is: GET /users/<id | userPrincipalName>/messages
However, there doesn't appear to be a method in the SDK for it.
I can get users with this:
IGraphServiceUsersCollectionPage filteredUsers =
graphApi.Users.Request()
.Filter("userPrincipalName eq 'user#domain.com'")
.GetAsync().Result;
When I loop through the collection, I can see that the User has a 'Messages' property, but it is always null.
If I manually build a request message with HttpClient I can get the messages.
The second problem is that the Recipient property is always the userPrincipalName of the mailbox. How can I get the alias that was used by the sender?
While you are able to get your collection of users successfully, you have to make another request to receive the messages. This would look something like:
IUserMessagesCollectionPage userMessages =
graphApi.Users["user_id"].Messages.Request()
.GetAsync().Result;
To answer your second question, at this time you cannot access the original recipient through the Graph API, but you can do this through EWS. This is due to the fact that you can only retrieve the SMTP message headers through EWS. You can read more about how to do this here.
If this is something you believe is valuable to you in the Graph, I would encourage you to post it in our UserVoice.
If you want to get the email as a file, you can simply get the body as bytes through the SDK:
byte[] asBytes = Encoding.Unicode.GetBytes(message.Body.ToString());

Can BigCommerce Private Apps use OAuth

I am very confused by the BC documentation on their API, because they let you create "Draft Apps" (private apps) and now I see that in their documentation they say "We do not currently provide a means of keeping OAuth apps private.".
My concern here is that they made some changes recently that might have affected a few of my Private Apps that I had running just fine a month ago. If anyone can provide some insight, I would appreciate it greatly!
https://developer.bigcommerce.com/api/guides/oauth-transition
There is nothing wrong with creating oAuth credentials with a "Draft App" for the sole purpose of accessing the API of your store. You do not ever have to publish your app and your app will never be made "public" in that case. You also don't have to bother with the 'Load Callback URL' and filling out the details on your draft app, unless you want to provide yourself an interface in the store.
The "Draft App" function was specifically meant to allow Developers building apps for the BC App Marketplace to test their apps in a store before submission. However, you can use it to make a private application that is only intended for your store - I'm including the process here for others!
Making a Private App with oAuth (or How to Generate oAuth Credentials for a Store)
What you will need
Access to the account listed as the "store owner" of the store where you want to install your app or the ability to get a person with access to complete a couple steps
Ability to setup a local or public URL to receive the 'Auth Callback Request'
Getting started
The first thing you should do is sort out making available a local or public URL that can receive an "Auth Callback" request. This resource must be able to work over an HTTPS connection but the SSL can be self-signed. The 'Auth Callback' request from Bigcommerce is a GET request that will have 3 query parameters on the URL: code, scope, and context.
It is described in greater detail here:
https://developer.bigcommerce.com/api/callback#get-req
Additional info
When building a public app it is important that the service receiving the Auth Callback request be configured to catch the 3 query values and combine them with information you already have. You would then send all of this information in a POST to the BC oAuth Token service to generate your API token for the store. In addition to that you would want to respond to the Auth Callback request with a 200 status and an interface, or instructions, for the user.
In the context of building a private application you don't need to worry about any of that. All you need to do is capture the query values. If you have this already then go ahead and jump down to the section on generating an API token below.
Before Moving On
You should have a URL path that can receive a GET request and captures query parameters. Test it out and make sure it works. Here are a couple example URLs:
https://example.com/auth-callback
https://localhost:8000/auth-service
Registering an App
The key point here is that the registration of the app must be completed by the store owner account of the store where you want to install the app. If you have access to the store owner account credentials then follow the steps at the bottom of this page:
https://developer.bigcommerce.com/api/registration
If you are working with the store owner then you can direct them to complete the steps above. You will need to provide them the Auth Callback URL you created for completing Step 9. The Load Callback URL does have to be filled in but the default example provided can be left in place.
SCOPES
When registering an app you are able to choose the scopes for the app. It is simple to just leave them all open but it is best practice to only enable the scopes you need. Here is a list of the scopes:
https://developer.bigcommerce.com/api/scopes
If you are not sure whether or not you will need a certain scope then leave it enabled because you will have to re-generate your API Token (perform a re-install of the app) if you have to change the scopes on your app.
Before Moving On
You need to have the client_id and client_secret. If someone else registered the app then you will need to ask them for this. There is a View Client ID button that will provide it after registering an app.
Generate the Auth Callback Request
You will need the person with store owner access again for this step. They will need to login to their store and go to the Apps section on the left side column. After that click on Marketplace -> then My Apps (in the top-right) -> then My Draft Apps
You should now see a list containing any "apps" that the store owner has registered. Choose the one relating to the client_id you plan to use. Click to install the app.
The Auth Callback request has now been sent and you are done here. You should expect to see just a blank or grey page as a result unless you are responding to the Auth Callback request with content. Your app is now awaiting authentication.
If using a self-signed certificate
When your Auth Callback URL has a self-signed certificate then you will see a "untrusted cert" error in your browser when you attempt installation of the app. You should choose to trust the certificate and continue.
Before Moving On
You should now have received the code, scope, and context at your Auth Callback URL. If you did not it was likely due to not having SSL/TLS at your server. You can replay the Auth Callback request as many times as needed by Cancelling Installation of the app in the same place where you started it. You can even open up a Dev tool and watch the request happen to see what errors show up in the console. If this is continuing to fail then you should reach out to Bigcommerce support or ask a new question on here!
Generating an API Token to Complete Installation
Follow the steps here:
https://developer.bigcommerce.com/api/callback#post-req
You should have all of the details needed to send a POST request to the BC Auth Token Service at https://login.bigcommerce.com/oauth2/token
Make sure to URL encode your content and you should be good! Here is a site that can URL encode and decode for you: http://meyerweb.com/eric/tools/dencoder/
Just be careful of it encoding & and = signs when those are actually being used as separators between fields or between field/value (respectively).
Before Moving On
You should have received a successful response from the Auth service which will include your API Token. Once you have this you are all set to access the API of the store. You no longer need to have your Auth Callback URL up and available and can take that down.
Also take note of the context to use to create your API path.
Accessing the API
Now that you have your API Token and context you are all set to access the API of a store. Start off with a simple request to the /time endpoint.
Make a GET request and include the following headers (minus the curly braces):
X-Auth-Client: {CLIENT_ID}
X-Auth-Token: {API_TOKEN}
Accept: application/json
Content-Type: application/json
Send your request to a URL path of (minus curly braces):
https://api.bigcommerce.com/{context}/v2/time
If you get back a 200 response then you are all set!
Additional Notes - Ways to Break Credentials
Once you have successfully generated an API Token for a certain app, that app will display in the Control Panel as an icon in the Apps section. The fact the app is there shows it is installed and allowing access. If you uninstall that app then the previously generated API token will stop working.
Changing the scopes on an already installed app will require it to be re-installed to correct the token.
Changing the store owner email on the store will cause the token to stop working. The API Token is specifically tied to the store owner that registered the app.
if you have apps in "My draft apps" and you used basic oauth, you will have to change to Oauth Authentication, but if only have private apps using "legacy api account", you will not need to change.

How to use Stripe Connect in an iOS app

Has anyone had success using Stripe connect with an iOS app. I have a few questions:
I'm following the guidelines here: https://stripe.com/docs/connect/getting-started
Registering an Application: easy, no problem here
Then a little further down:
Send your users to Stripe: again, easy no problem here, I just have a button that opens up the link in a UIWebView. I assume having the client_id in the URL is fine? A lot of my uncertainty is what IDs/keys I should hard-code into the app
Then a little further down:
After the user connects or creates a Stripe account, we'll redirect them back to the redirect_uri you set in yourapplication settings with a code parameter or an error.
What I'm doing here is using the UIWebview's webView:shouldStartLoadWithReqest:navigationType delegate method to check for the string "code=" in the URL. If it finds that, then I'm able to grab the "code" parameter. So in reality, the redirect_uri is completely unnecessary for me. Is this the right way to handle this? Should I be doing this within my app or on my server?
After receiving the code, we are supposed to make a POST call to receive an access_token. Again, should this be done within the app or on the Server? It requires the use of a secret_key, so I'm guessing server? And how do I send credit card information along with this token if the token needs to be sent to the server? I know how to obtain the card number, exp date, and CVV. But in terms of passing it to the server (with or without the token) is something I'm not sure of.
Then when it comes to actually writing PHP, Ruby, or Python code on the server, I'm at a total loss.
Any help would be greatly appreciated.
You should setup a small web app to create stripe charges and storing you customers Authorization Code. Configure two routes in your web app for redirect_uri and webhook_uri and add the url in your Stripe Apps settings. The charges should be created from a server side app because it requires the secret_key / authorization_code which should not be stored in an iPad app. Otherwise they may lead to a security leak. I'm trying to describe the concept below:
Provide the stripe connect button in your app and set the link to open in Safari (not in an web view). You should add a state parameter to the url with an id which is unique to your users.
On tapping the button your user will be redirected to Stripe where s/he will be asked to authorize your application. Upon authorization stripe will hit your redirect_uri with a authorization_code and the state you previously provided. Do a post call according to Stripe Documentation with the authorization_code to get an access_token. Store the access_token mapped with the state in a database.
Define a custom url scheme in your app. Invoke the custom url from your web app. The user supposed to open the url in mobile safari. So invoking the custom url will reopen your application. You can pass an additional parameter to indicate failure / success. In your app update the view based on this parameter.
Now you are all set to create a charge on your server on behalf of the iPad user. Use stripe iOS sdk to generate a card_token from the card information. It'll require your stripe publishable_key. Then define an api in your web app which takes 3 parameters: card_token, user_id and amount. Call this api from your iPad app whenever you want to create a charge. You can also encrypt this information with a key if you're worried about security using any standard encryption method. You can easily decrypt the info in your web app as you know the key.
When this api is called from the iPad app you'll receive the user_id (which you saved as state previously), card_token and amount. Retrieve the access_token mapped to the user_id (or state). You can then made a charge on behalf of the user using the access_token, card_token and amount.
You can use ruby / php / python / node in the server as Stripe provides sdk for them. I assume other languages can be used as well as there is a REST interface.
Please note that this is just a concept. It should work like it but I haven't implemented it yet. I'll update this answer with sample code when I'm done.
You can use UIWebView. You will still need to use redirect urls and monitor the redirect using the delegate "webView:shouldStartLoadWithRequest:navigationType:"

Primer on Getting Started

I'm just getting started with D2L and am running into problems.
On the "Getting Started" page, I have completed the first three steps:
1) Acquire an App Key/ID pair from D2L - I have received the App ID and App Key
2) Create a test account in your host LMS - I have created a new user account with the administrator role for testing
3) Choose a client library to work with - I am using the PHP SDK
4) Authenticate with your LMS - This is where I'm running into trouble.
When I use the Getting Started sample:
http://samples.valence.desire2learn.com/samples/GettingStartedSample/
And enter my host, app ID and app key and hit on the "Authenticate" button, I get a "This application is not authorized on this LMS instance. Ask your administrator to authorize this application" error.
I am an administrator on my D2L host and I'm not sure how to authorize my own app.
I have tried the following:
Navigating to the "Manage Extensibility" page because that's where D2L says my app should be located, but it isn't there.
Enabling the API (d2l.Security.Api.EnableApi) under the "DOME" page to no avail.
What am I doing wrong?
Based on your question and comments, there were two issues here:
First is that the list of App ID/Key pairs appropriate for your LMS get regularly fetched by your LMS from the D2L KeyTool service. The schedule for this fetching is once a day; accordingly, if the scheduled task isn't set up, or if your LMS isn't identifying itself properly to the KeyTool service, or if time hasn't yet elapsed after key granting to the next scheduled run of the task, the App won't yet be in your LMS' Manage Extensibility list. It sounds like you no longer have that issue.
Second is that the Valence Learning Framework APIs' authentication process (requesting and retrieving a set of user tokens for an LMS user) requires several LMS features to be properly set up: (a) the LMS must be configured to support Deep Linking, (b) the LMS must be set up to handle the ?target= parameter on incoming client URL requests, and curate that parameter throughout the user authentication process.
In cases where your LMS is not doing the user authentication but depending upon another, third-party IDP (like Shibboleth), any ?target= parameter passed into the login process must be taken care of by the IDP and properly handed back to the LMS after user authentication. In a situation where you have multiple redirections occurring during user authentication, this can involve successive generation of a target parameter, and each generation must re-URL-encode the previous request URL in its entirety (like sticking an envelope inside another envelope, inside yet another envelope).
If your LMS is not properly configured to support these two points, which you might not notice during other operations, then client calls to the Learning Framework APIs won't work because the calling client won't be able to fetch back a set of user tokens.
To solve the second of these issues, you may have to contact D2L's Customer Support desk -- they can verify, and adjust as necessary, the LMS configuration part of this authentication chain. If you're integrating your LMS with other third-party IDP components not administered or deployed by D2L, then you might also need to adjust their configurations: D2L can likely advise on what needs to be done there (curate the target parameter on URls), but cannot adjust the configuration for you in those cases.

Resources