Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ go.work
data/
config.json
temp/
.DS_STORE
.DS_STORE
.gocache
4 changes: 2 additions & 2 deletions cmd/api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,8 @@ func initializeRoutes(engine *gin.Engine) {
engine.GET("/v2/server/stats/mostplayed", handlers.CreateHandler(handlers.GetWeeklyMostPlayedMapsets))

// Download
engine.GET("/v2/download/map/:id", handlers.CreateHandler(handlers.DownloadQua))
engine.GET("/v2/download/replay/:id", handlers.CreateHandler(handlers.DownloadReplay))
engine.GET("/v2/download/map/:id", middleware.RequireAuthOrClientSecret, handlers.CreateHandler(handlers.DownloadQua))
engine.GET("/v2/download/replay/:id", middleware.RequireAuthOrClientSecret, handlers.CreateHandler(handlers.DownloadReplay))
engine.Match([]string{"GET", "HEAD"}, "/v2/download/mapset/:id", middleware.RequireAuth, handlers.CreateHandler(handlers.DownloadMapset))
engine.POST("/v2/download/multiplayer/:id/upload", middleware.RequireAuth, handlers.CreateHandler(handlers.UploadMultiplayerMapset))
engine.Match([]string{"GET", "HEAD"}, "/v2/download/multiplayer/:id", middleware.RequireAuth, handlers.CreateHandler(handlers.DownloadMultiplayerMapset))
Expand Down
15 changes: 15 additions & 0 deletions db/applications.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,21 @@ func GetApplicationById(id int) (*Application, error) {
return application, nil
}

// GetActiveApplicationByClientSecret Retrieves an active application by client secret
func GetActiveApplicationByClientSecret(secret string) (*Application, error) {
var application *Application

result := SQL.
Where("client_secret = ? AND active = 1", secret).
First(&application)

if result.Error != nil {
return nil, result.Error
}

return application, nil
}

// SetActiveStatus Sets an applications active status
func (app *Application) SetActiveStatus(active bool) error {
app.Active = active
Expand Down
56 changes: 55 additions & 1 deletion middleware/authentication.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ type JWTClaims struct {
}

const (
messageNoHeader = "You must provide a valid `Authorization` or `auth` header."
messageNoHeader = "You must provide a valid `Authorization` or `auth` header."
messageNoAuthOrSecret = "You must provide a valid `Authorization`, `auth`, or `client_secret`."
)

// RequireAuth Middleware authentication function
Expand All @@ -42,6 +43,38 @@ func RequireAuth(c *gin.Context) {
c.Next()
}

// RequireAuthOrClientSecret requires either user authentication or a valid application client secret.
func RequireAuthOrClientSecret(c *gin.Context) {
user, authErr := authenticateUser(c)

if authErr == nil {
c.Set("user", user)
c.Next()
return
}

_, secretErr := authenticateApplicationClientSecret(c)

if secretErr == nil {
c.Next()
return
}

apiErr := authErr

if authErr.Message == messageNoHeader && secretErr.Error == gorm.ErrRecordNotFound {
apiErr = &handlers.APIError{Status: http.StatusUnauthorized, Message: messageNoAuthOrSecret}
} else if secretErr.Error != gorm.ErrRecordNotFound {
apiErr = secretErr
}

handlers.CreateHandler(func(ctx *gin.Context) *handlers.APIError {
return apiErr
})(c)

c.Abort()
}

// AllowAuth Allows user authentication but does not require it. This middleware fails
// in the event that the user passes in an invalid token OR some other error
func AllowAuth(c *gin.Context) {
Expand Down Expand Up @@ -94,6 +127,27 @@ func authenticateUser(c *gin.Context) (*db.User, *handlers.APIError) {
return user, nil
}

// authenticateApplicationClientSecret authenticates an active application client secret.
func authenticateApplicationClientSecret(c *gin.Context) (*db.Application, *handlers.APIError) {
secret := c.GetHeader("client_secret")

if secret == "" {
return nil, &handlers.APIError{Status: http.StatusUnauthorized, Message: messageNoAuthOrSecret, Error: gorm.ErrRecordNotFound}
}

application, err := db.GetActiveApplicationByClientSecret(secret)

if err != nil && err != gorm.ErrRecordNotFound {
return nil, handlers.APIErrorServerError("Error occurred while authenticating application", err)
}

if application == nil {
return nil, &handlers.APIError{Status: http.StatusUnauthorized, Message: "You are unauthorized to access this resource.", Error: gorm.ErrRecordNotFound}
}

return application, nil
}

// AuthenticateInGameRequest authenticates a request using the in-game auth header.
func AuthenticateInGameRequest(c *gin.Context) (*db.User, error) {
token := c.GetHeader("auth")
Expand Down
Loading