sharded-gotify/auth/authentication.go

119 lines
3.4 KiB
Go

package auth
import (
"errors"
"strings"
"github.com/gin-gonic/gin"
"github.com/jmattheis/memo/model"
)
const (
headerName = "Authorization"
headerSchema = "ApiKey "
)
// The Database interface for encapsulating database access.
type Database interface {
GetApplicationByID(id string) *model.Application
GetClientByID(id string) *model.Client
GetUserByName(name string) *model.User
GetUserByID(id uint) *model.User
}
// Auth is the provider for authentication middleware
type Auth struct {
DB Database
}
type authenticate func(tokenID string, user *model.User) (authenticated bool, success bool, userId uint)
// RequireAdmin returns a gin middleware which requires a client token or basic authentication header to be supplied
// with the request. Also the authenticated user must be an administrator.
func (a *Auth) RequireAdmin() gin.HandlerFunc {
return a.requireToken(func(tokenID string, user *model.User) (bool, bool, uint) {
if user != nil {
return true, user.Admin, user.ID
}
if token := a.DB.GetClientByID(tokenID); token != nil {
return true, a.DB.GetUserByID(token.UserID).Admin, token.UserID
}
return false, false, 0
})
}
// RequireClient returns a gin middleware which requires a client token or basic authentication header to be supplied
// with the request.
func (a *Auth) RequireClient() gin.HandlerFunc {
return a.requireToken(func(tokenID string, user *model.User) (bool, bool, uint) {
if user != nil {
return true, true, user.ID
}
if token := a.DB.GetClientByID(tokenID); token != nil {
return true, true, token.UserID
}
return false, false, 0
})
}
// RequireApplicationToken returns a gin middleware which requires an application token to be supplied with the request.
func (a *Auth) RequireApplicationToken() gin.HandlerFunc {
return a.requireToken(func(tokenID string, user *model.User) (bool, bool, uint) {
if user != nil {
return true, false, 0
}
if token := a.DB.GetApplicationByID(tokenID); token != nil {
return true, true, token.UserID
}
return false, false, 0
})
}
func (a *Auth) tokenFromQueryOrHeader(ctx *gin.Context) string {
if token := a.tokenFromQuery(ctx); token != "" {
return token
} else if token := a.tokenFromHeader(ctx); token != "" {
return token
}
return ""
}
func (a *Auth) tokenFromQuery(ctx *gin.Context) string {
return ctx.Request.URL.Query().Get("token")
}
func (a *Auth) tokenFromHeader(ctx *gin.Context) string {
if header := ctx.Request.Header.Get(headerName); header != "" && strings.HasPrefix(header, headerSchema) {
return strings.TrimPrefix(header, headerSchema)
}
return ""
}
func (a *Auth) userFromBasicAuth(ctx *gin.Context) *model.User {
if name, pass, ok := ctx.Request.BasicAuth(); ok {
if user := a.DB.GetUserByName(name); user != nil && ComparePassword(user.Pass, []byte(pass)) {
return user
}
}
return nil
}
func (a *Auth) requireToken(auth authenticate) gin.HandlerFunc {
return func(ctx *gin.Context) {
token := a.tokenFromQueryOrHeader(ctx)
user := a.userFromBasicAuth(ctx)
if user != nil || token != "" {
if authenticated, ok, userID := auth(token, user); ok {
RegisterAuthentication(ctx, user, userID, token)
ctx.Next()
return
} else if authenticated {
ctx.AbortWithError(403, errors.New("you are not allowed to access this api"))
return
}
}
ctx.AbortWithError(401, errors.New("you need to provide a valid access token or user credentials to access this api"))
}
}