Help us shape the future of Go! Take the 2026 Go Developer Survey

slackauth

package
v0.13.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 24, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

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

View Source
const KeychainService = "workflow-slack"

KeychainService is the name the credentials are kept under in the keychain.

Variables

View Source
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")
)
View Source
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.

View Source
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.

View Source
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

func FileLock(path string) func(ctx context.Context) (func(), error)

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

type Refresher struct {
	Do       httpx.Doer
	Base     string
	ClientID string
	Now      func() time.Time
}

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

func (s Source) Token(ctx context.Context, expired config.Secret) (config.Secret, error)

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

func Choose(goos string, cfg config.Config, item keychain.Item) Store

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

func FileStore(files config.Files) Store

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

func KeychainStore(item keychain.Item) Store

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.

func (Store) Save

func (s Store) Save(ctx context.Context, credentials Credentials) error

Save keeps credentials in place of any kept before.

func (Store) Where

func (s Store) Where() string

Where says where the credentials are kept, for doctor and the login to say.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL