mirror of https://github.com/writeas/writefreely
A focused writing and publishing space.
https://write.with.parts
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
149 lines
4.0 KiB
149 lines
4.0 KiB
5 years ago
|
package writefreely
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"github.com/writeas/slug"
|
||
|
"net/http"
|
||
|
"net/url"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
type slackOauthClient struct {
|
||
|
ClientID string
|
||
|
ClientSecret string
|
||
|
TeamID string
|
||
|
CallbackLocation string
|
||
|
HttpClient HttpClient
|
||
|
}
|
||
|
|
||
|
type slackExchangeResponse struct {
|
||
|
AccessToken string `json:"access_token"`
|
||
|
Scope string `json:"scope"`
|
||
|
TeamName string `json:"team_name"`
|
||
|
TeamID string `json:"team_id"`
|
||
|
}
|
||
|
|
||
|
type slackIdentity struct {
|
||
|
Name string `json:"name"`
|
||
|
ID string `json:"id"`
|
||
|
Email string `json:"email"`
|
||
|
}
|
||
|
|
||
|
type slackTeam struct {
|
||
|
Name string `json:"name"`
|
||
|
ID string `json:"id"`
|
||
|
}
|
||
|
|
||
|
type slackUserIdentityResponse struct {
|
||
|
OK bool `json:"ok"`
|
||
|
User slackIdentity `json:"user"`
|
||
|
Team slackTeam `json:"team"`
|
||
|
Error string `json:"error"`
|
||
|
}
|
||
|
|
||
|
const (
|
||
|
slackAuthLocation = "https://slack.com/oauth/authorize"
|
||
|
slackExchangeLocation = "https://slack.com/api/oauth.access"
|
||
|
slackIdentityLocation = "https://slack.com/api/users.identity"
|
||
|
)
|
||
|
|
||
|
var _ oauthClient = slackOauthClient{}
|
||
|
|
||
|
func (c slackOauthClient) GetProvider() string {
|
||
|
return "slack"
|
||
|
}
|
||
|
|
||
|
func (c slackOauthClient) GetClientID() string {
|
||
|
return c.ClientID
|
||
|
}
|
||
|
|
||
|
func (c slackOauthClient) buildLoginURL(state string) (string, error) {
|
||
|
u, err := url.Parse(slackAuthLocation)
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
q := u.Query()
|
||
|
q.Set("client_id", c.ClientID)
|
||
|
q.Set("scope", "identity.basic identity.email identity.team")
|
||
|
q.Set("redirect_uri", c.CallbackLocation)
|
||
|
q.Set("state", state)
|
||
|
|
||
|
// If this param is not set, the user can select which team they
|
||
|
// authenticate through and then we'd have to match the configured team
|
||
|
// against the profile get. That is extra work in the post-auth phase
|
||
|
// that we don't want to do.
|
||
|
q.Set("team", c.TeamID)
|
||
|
|
||
|
// The Slack OAuth docs don't explicitly list this one, but it is part of
|
||
|
// the spec, so we include it anyway.
|
||
|
q.Set("response_type", "code")
|
||
|
u.RawQuery = q.Encode()
|
||
|
return u.String(), nil
|
||
|
}
|
||
|
|
||
|
func (c slackOauthClient) exchangeOauthCode(ctx context.Context, code string) (*TokenResponse, error) {
|
||
|
form := url.Values{}
|
||
|
// The oauth.access documentation doesn't explicitly mention this
|
||
|
// parameter, but it is part of the spec, so we include it anyway.
|
||
|
// https://api.slack.com/methods/oauth.access
|
||
|
form.Add("grant_type", "authorization_code")
|
||
|
form.Add("redirect_uri", c.CallbackLocation)
|
||
|
form.Add("code", code)
|
||
|
req, err := http.NewRequest("POST", slackExchangeLocation, strings.NewReader(form.Encode()))
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
req.WithContext(ctx)
|
||
|
req.Header.Set("User-Agent", "writefreely")
|
||
|
req.Header.Set("Accept", "application/json")
|
||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||
|
req.SetBasicAuth(c.ClientID, c.ClientSecret)
|
||
|
|
||
|
resp, err := c.HttpClient.Do(req)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
var tokenResponse slackExchangeResponse
|
||
|
if err := limitedJsonUnmarshal(resp.Body, tokenRequestMaxLen, &tokenResponse); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return tokenResponse.TokenResponse(), nil
|
||
|
}
|
||
|
|
||
|
func (c slackOauthClient) inspectOauthAccessToken(ctx context.Context, accessToken string) (*InspectResponse, error) {
|
||
|
req, err := http.NewRequest("GET", slackIdentityLocation, nil)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
req.WithContext(ctx)
|
||
|
req.Header.Set("User-Agent", "writefreely")
|
||
|
req.Header.Set("Accept", "application/json")
|
||
|
req.Header.Set("Authorization", "Bearer "+accessToken)
|
||
|
|
||
|
resp, err := c.HttpClient.Do(req)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
var inspectResponse slackUserIdentityResponse
|
||
|
if err := limitedJsonUnmarshal(resp.Body, infoRequestMaxLen, &inspectResponse); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return inspectResponse.InspectResponse(), nil
|
||
|
}
|
||
|
|
||
|
func (resp slackUserIdentityResponse) InspectResponse() *InspectResponse {
|
||
|
return &InspectResponse{
|
||
|
UserID: resp.User.ID,
|
||
|
Username: slug.Make(resp.User.Name),
|
||
|
Email: resp.User.Email,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (resp slackExchangeResponse) TokenResponse() *TokenResponse {
|
||
|
return &TokenResponse{
|
||
|
AccessToken: resp.AccessToken,
|
||
|
}
|
||
|
}
|