client

package module
v0.2.12 Latest Latest
Warning

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

Go to latest
Published: Apr 1, 2026 License: MIT Imports: 15 Imported by: 4

README

Staffio client and general OAuth2 client

Features

  • OAuth2 Authorization Code Flow - Full OAuth2 authentication with Staffio identity provider
  • Session Management - Cookie-based sessions with configurable name/path/domain
  • Role-Based Access Control - Role verification during authentication callbacks
  • Token Refresh - Automatic token refresh support
  • Multiple Auth Mechanisms - Cookie, Header, or middleware-based authentication
  • User Info Extraction - Extract user from token response with priority handling (Me > User)
  • CSRF Protection - State token management for OAuth security
  • Flexible Configuration - Environment variables or runtime configuration
  • AJAX Support - Detects AJAX requests and returns appropriate responses

Environment Variables

# Required
OAUTH_CLIENT_ID=
OAUTH_CLIENT_SECRET=
OAUTH_PREFIX=https://staffio.work       # Staffio service URL

# Optional
OAUTH_URI_AUTHORIZE=/authorize
OAUTH_URI_TOKEN=/token
OAUTH_URI_INFO=/info/me
OAUTH_REDIRECT_URL=/auth/callback
OAUTH_SCOPES=openid
AUTH_COOKIE_NAME=_user                  # Session cookie name
AUTH_COOKIE_PATH=/
AUTH_COOKIE_DOMAIN=

User Type

User is a type alias for auth.User from the simpauth package. See simpauth for details.

Example


package main

import (
	"fmt"
	"net/http"

	staffio "github.com/liut/staffio-client"
)

func main() {

	loginPath := "/auth/login"
	staffio.SetLoginPath(loginPath)
	staffio.SetAdminPath("/admin")

	// login start
	http.HandleFunc(loginPath, staffio.LoginHandler)
	// default callback with role admin (role is optional)
	http.Handle("/auth/callback", staffio.AuthCodeCallback("admin"))

	// Or custom hook (ex: with chi)
	router.Route("/auth", func(r chi.Router) {
		r.Get("/login", staffio.LoginHandler)
		r.Get("/logout", staffio.LogoutHandler)
		handleTokenGot := func(ctx context.Context, w http.ResponseWriter, token *staffio.InfoToken) {
			// read token.AccessToken
			// write it into cookie or some db
		}
		handleSignedIn := func(ctx context.Context, w http.ResponseWriter, user *staffio.User) {
			// read user
			// write it into cookie or some db or response to frontend
			// AND SHOULD redirect to the URI of user panel (if not in AJAX)
		}
		cc := &staffio.CodeCallback{
			OnTokenGot: handleTokenGot,
			OnSignedIn: handleSignedIn,
		}
		r.Method(http.MethodGet, "/callback", cc.Handler())
	})

	// use middleware
	authF1 := staffio.Middleware()
	authF1 := staffio.Middleware(staffio.WithRefresh()) // auto refresh token time
	authF1 := staffio.Middleware(staffio.WithRefresh(), staffio.WithURI(loginPath)) // auto refresh and redirect
	http.Handle("/admin", authF1(http.HandlerFunc(handlerAdminWelcome)))
	// more handlers
}

func handlerAdminWelcome(w http.ResponseWriter, r *http.Request) {
	user := staffio.UserFromContext(r.Context())
	fmt.Fprintf(w, "welcome %s", user.Name)
}


// Middleware for gin
func Middleware(opts ...staffio.OptFunc) gin.HandlerFunc {
	option := staffio.NewOption(opts...)
	return func(c *gin.Context) {
		user, err := staffio.UserFromRequest(c.Request)
		if err != nil {
			if option.URI != "" {
				c.Redirect(http.StatusFound, option.URI)
			} else {
				c.AbortWithStatus(http.StatusUnauthorized)
			}
			return
		}
		if option.Refresh && user.NeedRefresh() {
			user.Refresh()
			user.Signin(c.Writer)
		}
		req := c.Request
		c.Request = req.WithContext(staffio.ContextWithUser(req.Context(), user))
		c.Next()
	}
}

// UserFromContext for gin
func UserFromContext(c *gin.Context) (user *User, ok bool) {
	return staffio.UserFromContext(c.Request.Context())
}

// AuthCodeCallback for gin handler which for Check auth with role[s] when auth-code callback
func AuthCodeCallback(roleName ...string) gin.HandlerFunc {
	return gin.WrapH(staffio.AuthCodeCallback(roleName...))
}


// HandlerShowMe for gin
func HandlerShowMe(c *gin.Context) {
	user, ok := staffio.UserFromContext(c.Request.Context())
	if !ok {
		c.AbortWithStatus(http.StatusUnauthorized)
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"me":    user,
	})
}
Making Authenticated API Requests
token := &staffio.O2Token{
	AccessToken: accessToken,
	TokenType:   "Bearer",
})
// RequestInfoToken gets user info with optional role filtering
infoToken, err := staffio.RequestInfoToken(ctx, token, "admin")
if err != nil {
    log.Fatal(err)
}
user, ok := infoToken.GetUser()
if ok {
    fmt.Println(user.Name)
}

// RequestWith makes authenticated GET requests to any URI
var result struct {
    ID    string `json:"id"`
    Title string `json:"title"`
}
err = staffio.RequestWith(ctx, "https://api.example.com/items/1", token, &result)
if err != nil {
    log.Fatal(err)
}
fmt.Println(result.Title)

Documentation

Index

Constants

View Source
const (
	TokenKey ctxKey = iota
)

Variables

View Source
var (
	UserFromRequest = auth.UserFromRequest
	UserFromContext = auth.UserFromContext
	ContextWithUser = auth.ContextWithUser

	NewAuth = auth.New
)

vars

View Source
var (
	ErrNoToken = errors.New("oauth2 token not found")
	ErrNoRole  = errors.New("the user not in special roles")

	AdminPath = "/admin/"
	LoginPath = "/auth/login"
)

Functions

func AuthCodeCallback

func AuthCodeCallback(roles ...string) http.Handler

AuthCodeCallback Handler for Check auth with role[s] when auth-code callback

func AuthCodeCallbackWrap

func AuthCodeCallbackWrap(next http.Handler) http.Handler

AuthCodeCallbackWrap is a middleware that injects a InfoToken with roles into the context of callback request

func AuthMiddleware

func AuthMiddleware(redirect bool) func(next http.Handler) http.Handler

AuthMiddleware ...

func FixURI added in v0.2.9

func FixURI(pre, s string) string

func GetPrefix added in v0.2.5

func GetPrefix() string

func IsAjax added in v0.2.6

func IsAjax(r *http.Request) bool

IsAjax Check if is AJAX Request for json data

func LoginHandler

func LoginHandler(w http.ResponseWriter, r *http.Request)

LoginHandler handles login requests. For Ajax requests, returns authorization form data; otherwise redirects to the authorization page or displays a login page.

func LoginStart added in v0.1.13

func LoginStart(w http.ResponseWriter, r *http.Request) string

LoginStart generate state into cookie and return redirectURI

func LogoutHandler added in v0.1.3

func LogoutHandler(w http.ResponseWriter, r *http.Request)

LogoutHandler ...

func Middleware added in v0.1.1

func Middleware(opts ...auth.OptFunc) func(next http.Handler) http.Handler

Middleware returns an HTTP middleware with additional options.

func MiddlewareWordy added in v0.1.8

func MiddlewareWordy(redir bool) func(next http.Handler) http.Handler

MiddlewareWordy returns an HTTP middleware with optional redirect behavior.

func RegisterStateStore added in v0.1.16

func RegisterStateStore(ss StateStore)

func RequestInfo added in v0.1.12

func RequestInfo(ctx context.Context, tok *oauth2.Token, obj any, parts ...string) error

RequestInfo calls the info API with the given token and unmarshals the response into obj. The optional parts are joined with "|" and appended to the info URI.

func RequestWith added in v0.2.9

func RequestWith(ctx context.Context, uri string, tok *oauth2.Token, obj any) error

RequestWith performs an HTTP GET request to the specified URI with the OAuth2 token and unmarshals the JSON response into obj.

func SetAdminPath

func SetAdminPath(path string)

func SetLoginPath

func SetLoginPath(path string)

func SetupClient added in v0.1.15

func SetupClient(conf *oauth2.Config, clientID, clientSecret string)

Setup oauth2 config

func SetupRedirectURL added in v0.1.15

func SetupRedirectURL(conf *oauth2.Config, s string)

func SetupScopes added in v0.1.15

func SetupScopes(conf *oauth2.Config, scopes []string)

func Signin added in v0.1.8

func Signin(user UserEncoder, w http.ResponseWriter)

Signin signs in the user by encoding user info into a cookie.

func Signout added in v0.1.3

func Signout(w http.ResponseWriter)

Signout signs out the user by clearing the user cookie.

func StateGet added in v0.1.12

func StateGet(r *http.Request) string

func StateSet added in v0.1.12

func StateSet(w http.ResponseWriter, state string)

func StateUnset added in v0.1.12

func StateUnset(w http.ResponseWriter)

func TokenFromContext

func TokenFromContext(ctx context.Context) *oauth2.Token

TokenFromContext returns a oauth2.Token from the given context if one is present. Returns nil if a oauth2.Token cannot be found. NOTE: This function is only effective for login callbacks processed by AuthCodeCallbackWrap.

func UidFromToken

func UidFromToken(tok *oauth2.Token) string

UidFromToken extract uid from oauth2.Token

func WithCookie added in v0.1.6

func WithCookie(name string, strs ...string) auth.OptFunc

WithCookie configures cookie-based session with the given name and optional attributes.

func WithHeader added in v0.2.8

func WithHeader(key string) auth.OptFunc

WithHeader configures header-based authentication with the given key (default: token).

func WithRefresh added in v0.1.1

func WithRefresh() auth.OptFunc

WithRefresh enables token refresh for the authorizer.

func WithURI added in v0.1.1

func WithURI(uri string) auth.OptFunc

WithURI sets the URI to redirect to after successful authentication.

Types

type AuthFormData added in v0.2.6

type AuthFormData struct {
	ResponseType string `json:"response_type"`
	ClientID     string `json:"client_id"`
	RedirectURI  string `json:"redirect_uri"`
	Scope        string `json:"scope"`
	State        string `json:"state"`
}

type Authorizer added in v0.1.6

type Authorizer = auth.Authorizer

Authorizer ...

type CodeCallback added in v0.1.6

type CodeCallback struct {
	// InRoles specifies the roles required for authorization.
	InRoles []string
	// OnTokenGot is called after receiving the infoToken from the provider.
	OnTokenGot TokenFunc
	// OnSignedIn is called after the user is signed in successfully.
	OnSignedIn UserFunc
}

CodeCallback handles OAuth2 authorization code callback with role checking.

func (*CodeCallback) Handler added in v0.1.6

func (cc *CodeCallback) Handler() http.Handler

Handler returns an HTTP handler that processes the callback request.

type IClient added in v0.2.3

type IClient interface {
	auth.Authorizer

	LoginStart(w http.ResponseWriter, r *http.Request) string
}

type IUser added in v0.2.12

type IUser interface {
	auth.IUser

	GetEmail() string
	GetPhone() string
}

type InfoError added in v0.1.11

type InfoError struct {
	ErrCode    string `json:"error,omitempty"`
	ErrMessage string `json:"error_description,omitempty"`
}

func (InfoError) GetError added in v0.1.16

func (e InfoError) GetError() error

type InfoToken

type InfoToken struct {
	InfoError

	AccessToken  string     `json:"access_token"`
	TokenType    string     `json:"token_type,omitempty"`
	RefreshToken string     `json:"refresh_token,omitempty"`
	ExpiresIn    int64      `json:"expires_in,omitempty"` // in seconds
	Expiry       time.Time  `json:"expiry,omitempty"`
	User         *O2User    `json:"user,omitempty"`
	Me           *Staff     `json:"me,omitempty"`
	Roles        auth.Names `json:"group,omitempty"`
	Meta         Meta       `json:"meta,omitempty"`
}

InfoToken ...

func AuthRequestWithRole

func AuthRequestWithRole(r *http.Request, role ...string) (it *InfoToken, err error)

AuthRequestWithRole called in AuthCallback

func RequestInfoToken

func RequestInfoToken(ctx context.Context, tok *oauth2.Token, roles ...string) (*InfoToken, error)

RequestInfoToken requests an InfoToken using the given token and optionally filters by roles.

func (*InfoToken) GetExpiry

func (tok *InfoToken) GetExpiry() time.Time

GetExpiry ...

func (*InfoToken) GetUser added in v0.2.7

func (it *InfoToken) GetUser() (*O2User, bool)

GetUser 从 InfoToken 中提取用户信息。 优先级:Me > User。当两者都为空时返回 false。 会通过 ToUser() 提取基础信息,补充 OID(使用 O2User.Sub)、Roles,并调用 Refresh() 更新缓存字段。

func (*InfoToken) HasRole added in v0.2.7

func (it *InfoToken) HasRole(slug string) bool

type Meta added in v0.2.11

type Meta map[string]any

func (Meta) Get added in v0.2.11

func (m Meta) Get(key string) (v any, ok bool)

Get ...

func (Meta) GetInt added in v0.2.11

func (m Meta) GetInt(key string) int

func (Meta) GetStr added in v0.2.11

func (m Meta) GetStr(key string) string

type O2Token added in v0.2.8

type O2Token = oauth2.Token

type O2User added in v0.2.6

type O2User struct {
	auth.User

	// Subject - Identifier for the User at the `SP`.
	// 主题 - `SP`对用户的标识符。
	Sub string `json:"sub,omitempty"`

	Email string `json:"email,omitempty"`
	Phone string `json:"phone,omitempty"`
}

UserInfo for OAuth2

func (O2User) GetEmail added in v0.2.11

func (ou O2User) GetEmail() string

func (O2User) GetPhone added in v0.2.11

func (ou O2User) GetPhone() string

func (O2User) ToUser added in v0.2.7

func (ou O2User) ToUser() User

type OptFunc added in v0.1.1

type OptFunc = auth.OptFunc

OptFunc ...

type RoleMe

type RoleMe map[string]any

func (RoleMe) Has

func (r RoleMe) Has(name string) bool

type Staff

type Staff struct {
	OID            string `json:"oid,omitempty" form:"oid"`           // pk id, ojecct id
	UID            string `json:"uid" form:"uid"`                     // 登录名
	CommonName     string `json:"cn,omitempty" form:"cn"`             // 全名
	GivenName      string `json:"gn,omitempty" form:"gn"`             // 名
	Surname        string `json:"sn,omitempty" form:"sn"`             // 姓
	Nickname       string `json:"nickname,omitempty" form:"nickname"` // 昵称
	Birthday       string `json:"birthday,omitempty" form:"birthday"` // 生日
	Gender         string `json:"gender,omitempty"`                   // 1=male, 2=female, 0=unknown
	Mobile         string `json:"mobile,omitempty"`                   // cell phone number
	Email          string `json:"email,omitempty"`
	EmployeeNumber string `json:"eid,omitempty" form:"eid"`
	EmployeeType   string `json:"etype,omitempty" form:"etitle"`
	AvatarPath     string `json:"avatarPath,omitempty" form:"avatar"`
	Provider       string `json:"provider,omitempty"`
}

Staff is a retrieved employee struct.

func (Staff) GetAvatar added in v0.2.7

func (s Staff) GetAvatar() string

func (Staff) GetEmail added in v0.2.12

func (s Staff) GetEmail() string

func (Staff) GetName added in v0.2.7

func (s Staff) GetName() string

func (Staff) GetOID added in v0.2.7

func (s Staff) GetOID() string

func (Staff) GetPhone added in v0.2.12

func (s Staff) GetPhone() string

func (Staff) GetUID added in v0.2.7

func (s Staff) GetUID() string

func (Staff) ToO2User added in v0.2.12

func (s Staff) ToO2User() (ou O2User)

func (Staff) ToUser added in v0.2.7

func (s Staff) ToUser() User

type StateStore added in v0.1.16

type StateStore interface {
	Save(w http.ResponseWriter, state string) error
	Verify(r *http.Request, state string) bool
	Wipe(w http.ResponseWriter, state string)
}

type TokenFunc added in v0.1.6

type TokenFunc = func(ctx context.Context, w http.ResponseWriter, it *InfoToken)

TokenFunc for custom read token

type User

type User = auth.User

User ...

type UserEncoder added in v0.1.6

type UserEncoder interface {
	auth.Encoder
	GetUID() string
	GetName() string
}

UserEncoder ...

type UserFunc added in v0.2.5

type UserFunc = func(ctx context.Context, w http.ResponseWriter, user *O2User)

UserFunc for custom read user

Jump to

Keyboard shortcuts

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