Page Menu
Home
Musing Studio
Search
Configure Global Search
Log In
Files
F10455902
oauth_slack.go
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
4 KB
Subscribers
None
oauth_slack.go
View Options
/*
* Copyright © 2019-2020 A Bunch Tell LLC.
*
* This file is part of WriteFreely.
*
* WriteFreely is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, included
* in the LICENSE file in this source code package.
*/
package
writefreely
import
(
"context"
"errors"
"fmt"
"github.com/writeas/nerds/store"
"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
{
OK
bool
`json:"ok"`
AccessToken
string
`json:"access_token"`
Scope
string
`json:"scope"`
TeamName
string
`json:"team_name"`
TeamID
string
`json:"team_id"`
Error
string
`json:"error"`
}
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
)
GetCallbackLocation
()
string
{
return
c
.
CallbackLocation
}
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
}
if
resp
.
StatusCode
!=
http
.
StatusOK
{
return
nil
,
errors
.
New
(
"unable to exchange code for access token"
)
}
var
tokenResponse
slackExchangeResponse
if
err
:=
limitedJsonUnmarshal
(
resp
.
Body
,
tokenRequestMaxLen
,
&
tokenResponse
);
err
!=
nil
{
return
nil
,
err
}
if
!
tokenResponse
.
OK
{
return
nil
,
errors
.
New
(
tokenResponse
.
Error
)
}
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
}
if
resp
.
StatusCode
!=
http
.
StatusOK
{
return
nil
,
errors
.
New
(
"unable to inspect access token"
)
}
var
inspectResponse
slackUserIdentityResponse
if
err
:=
limitedJsonUnmarshal
(
resp
.
Body
,
infoRequestMaxLen
,
&
inspectResponse
);
err
!=
nil
{
return
nil
,
err
}
if
!
inspectResponse
.
OK
{
return
nil
,
errors
.
New
(
inspectResponse
.
Error
)
}
return
inspectResponse
.
InspectResponse
(),
nil
}
func
(
resp
slackUserIdentityResponse
)
InspectResponse
()
*
InspectResponse
{
return
&
InspectResponse
{
UserID
:
resp
.
User
.
ID
,
Username
:
fmt
.
Sprintf
(
"%s-%s"
,
slug
.
Make
(
resp
.
User
.
Name
),
store
.
GenerateRandomString
(
"0123456789bcdfghjklmnpqrstvwxyz"
,
5
)),
DisplayName
:
resp
.
User
.
Name
,
Email
:
resp
.
User
.
Email
,
}
}
func
(
resp
slackExchangeResponse
)
TokenResponse
()
*
TokenResponse
{
return
&
TokenResponse
{
AccessToken
:
resp
.
AccessToken
,
}
}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Feb 1, 8:14 AM (1 d, 3 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3146047
Attached To
rWF WriteFreely
Event Timeline
Log In to Comment