package auth import ( "errors" "strings" "github.com/gin-gonic/gin" "github.com/gotify/server/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")) } }