Documentation
¶
Overview ¶
Package slackauth keeps a Slack user token posting past the twelve hours a rotating token lasts: it refreshes the token through Slack's oauth.v2.access, keeps each new pair where the configuration says — the macOS keychain or the configuration file — and hands out a token with time left on it, refreshing under a lock so two processes do not spend the same single-use refresh token.
Index ¶
Constants ¶
const KeychainService = "workflow-slack"
KeychainService is the name the credentials are kept under in the keychain.
Variables ¶
var ( // ErrRefreshRefused reports Slack turning a refresh down, in its own code — // invalid_refresh_token, bad_client_secret and the like. ErrRefreshRefused = errors.New("refreshing the Slack user token was refused") // ErrNotAUserToken reports a refresh that answered with a token other than // a user token, which is the only kind workflow posts with. ErrNotAUserToken = errors.New("refreshing gave no Slack user token") // ErrUnreachable reports a refresh that never reached Slack, or an answer // that could not be read. ErrUnreachable = errors.New("could not reach Slack to refresh the user token") // ErrUnexpectedStatus reports a refresh Slack answered with a status other // than the 200 its every verdict comes with, or the 429 that asks to wait. ErrUnexpectedStatus = errors.New("slack answered the refresh with an unexpected status") )
var ErrLocked = errors.New("another workflow is refreshing the Slack token; try again in a minute")
ErrLocked reports a refresh lock another process held for as long as this one would wait.
var ErrNotKept = errors.New("slack gave a new token that could not be kept; run `workflow slack login` again")
ErrNotKept reports a new pair Slack gave that could not be kept: the refresh token it replaced is spent, so only a fresh login brings posting back.
var ErrNotLoggedIn = errors.New("no Slack user token is set up; run `workflow slack login`")
ErrNotLoggedIn reports that no Slack user token has been set up where the configuration says it is kept; `workflow slack login` sets one up.
Functions ¶
func FileLock ¶
FileLock is a lock held on the file at path: one process at a time holds it, across every workflow running, for as long as it lives or until it unlocks. The system lets go of a lock whose holder ended, so a lock file left behind is taken at once, and one held for however long is never taken over. A wait gives up after lockWait or when ctx ends.
Types ¶
type Credentials ¶
type Credentials struct {
ClientSecret config.Secret
RefreshToken config.Secret
AccessToken config.Secret
ExpiresAt time.Time
}
Credentials are a Slack user token's rotating credentials: the app's client secret, which every refresh is made with, and the access and refresh tokens each refresh replaces, with when the access token expires.
type Refresher ¶
Refresher refreshes a user token through Slack's API at Base, for the app ClientID names, telling the time with Now.
func (Refresher) Refresh ¶
func (r Refresher) Refresh(ctx context.Context, credentials Credentials) (Credentials, error)
Refresh swaps credentials' refresh token for a new pair, sending the client ID and secret as Basic authentication, as Slack asks, rather than in the form. The refresh token it used is spent, so the pair it returns is the one to keep. No credential reaches an error it returns.
type Source ¶
type Source struct {
Store Store
Refresher Refresher
Lock func(ctx context.Context) (func(), error)
Now func() time.Time
}
Source hands out the user token to post with: the one kept in Store while it has time left, otherwise a new one from Refresher, kept in its place. A refresh is made under Lock, so while each process that refreshes takes it, two never spend the same refresh token, which works once.
func (Source) Token ¶
Token is a user token with more than refreshAhead left on it, and other than expired — the token Slack just answered token_expired for, or empty when none was. A process that waited on the lock re-reads what the one before it kept, and refreshes only when that is still not good enough.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is where a user token's credentials are kept: the macOS keychain, or the configuration file.
func Choose ¶
Choose is where the configuration keeps a user token's credentials: in the configuration file when it already holds them, or on any system but macOS, which has no keychain workflow drives; otherwise in item, the macOS keychain.
func FileStore ¶
FileStore keeps the credentials in one of the configuration files: the one that already holds them, or else the home directory's, so a first login puts no secret in a file in a working tree that can do without one. It rewrites only their fields and keeps every other setting as it is.
func KeychainStore ¶
KeychainStore keeps the credentials in item, one line of JSON in the macOS keychain.
func NewStore ¶
func NewStore( where string, load func(context.Context) (Credentials, error), save func(context.Context, Credentials) error, ) Store
NewStore is a Store that keeps credentials through load and save, described as where.
func (Store) Keep ¶
func (s Store) Keep(ctx context.Context, renewed Credentials) error
Keep saves a pair Slack just gave, trying again where a save fails, since the refresh token it replaced is already spent; ErrNotKept says it could not.
func (Store) Load ¶
func (s Store) Load(ctx context.Context) (Credentials, error)
Load reads the credentials, or ErrNotLoggedIn when none are kept.