Add endpoint to authenticate a user

This commit is contained in:
2020-09-04 16:32:29 +10:00
parent b902cf5183
commit 41643fb01b
8 changed files with 179 additions and 10 deletions
+1
View File
@@ -9,6 +9,7 @@ require (
github.com/gin-gonic/gin v1.6.3
github.com/go-playground/validator/v10 v10.3.0 // indirect
github.com/jinzhu/gorm v1.9.16
github.com/oklog/ulid v1.3.1
github.com/oklog/ulid/v2 v2.0.2
github.com/prometheus/client_golang v1.7.1
github.com/prometheus/common v0.10.0
+4
View File
@@ -11,8 +11,10 @@ github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY
github.com/ajstarks/svgo v0.0.0-20200320125537-f189e35d30ca h1:kWzLcty5V2rzOqJM7Tp/MfSX0RMSI1x4IOLApEefYxA=
github.com/ajstarks/svgo v0.0.0-20200320125537-f189e35d30ca/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
@@ -183,6 +185,7 @@ github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ
github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/oklog/ulid/v2 v2.0.2 h1:r4fFzBm+bv0wNKNh5eXTwU7i85y5x+uwkxCUTNVQqLc=
github.com/oklog/ulid/v2 v2.0.2/go.mod h1:mtBL0Qe/0HAx6/a4Z30qxVIAL1eQDweXq5lxOEiwQ68=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -337,6 +340,7 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
+1 -9
View File
@@ -5,7 +5,6 @@ import (
"encoding/json"
"errors"
"fmt"
"math/rand"
"net/http"
"strconv"
"time"
@@ -15,7 +14,6 @@ import (
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
"github.com/jinzhu/gorm/dialects/postgres"
"github.com/oklog/ulid/v2"
"github.com/prometheus/client_golang/api"
v1 "github.com/prometheus/client_golang/api/prometheus/v1"
"github.com/prometheus/client_golang/prometheus"
@@ -193,7 +191,7 @@ func (handler *BadgesHandler) queryBadgeFromDB(queryName string) (*models.Badge,
}
func (handler *BadgesHandler) CreateBadge(c *gin.Context) {
newID := newULID()
newID := NewULID()
jsonB := new(postgres.Jsonb)
options := models.PrometheusOptions{
ValueField: "",
@@ -229,12 +227,6 @@ func (handler *BadgesHandler) CreateBadge(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"id": newID})
}
func newULID() string {
now := time.Now()
entropy := ulid.Monotonic(rand.New(rand.NewSource(now.UnixNano())), 0)
return ulid.MustNew(ulid.Timestamp(now), entropy).String()
}
func queryMetric(client api.Client, options *models.PrometheusOptions) (*Metric, error) {
v1api := v1.NewAPI(client)
ctx, cancel := context.WithTimeout(context.Background(), metricRequestTimeout)
+18
View File
@@ -0,0 +1,18 @@
package handlers
import (
"math/rand"
"time"
"github.com/oklog/ulid"
)
func NewULID() string {
now := time.Now()
entropy := ulid.Monotonic(rand.New(rand.NewSource(now.UnixNano())), 0)
return ulid.MustNew(ulid.Timestamp(now), entropy).String()
}
type GeneralError struct {
Message string `json:"msg"`
}
+127
View File
@@ -0,0 +1,127 @@
package handlers
import (
"fmt"
"io/ioutil"
"net/http"
"time"
"github.com/getsentry/sentry-go"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
"github.com/prometheus/common/log"
"github.com/sirupsen/logrus"
"github.com/wahyd4/badger/models"
)
const (
githubHTTPTimeout = 10
)
type UsersHandler struct {
DB *gorm.DB
}
type UserAuthenticationRequest struct {
Name string `json:"name" binding:"required"`
GithubID string `json:"githubId" binding:"required"`
GithubUsername string `json:"githubUsername" binding:"required"`
GithubAvatarURL string `json:"githubAvatar" binding:"required"`
Email string `json:"email"`
Token string `json:"token" binding:"required"`
ExpiresAt string `json:"expiresAt"`
}
type UserInfo struct {
ID uint `json:"id"`
GithubID string `json:"githubId"`
}
func (usersHandler *UsersHandler) Authenticate(c *gin.Context) {
var requestBody UserAuthenticationRequest
if err := c.ShouldBindJSON(&requestBody); err != nil {
logrus.Error(err)
sentry.CaptureException(err)
c.JSON(http.StatusBadRequest, GeneralError{"invalid request body"})
return
}
if !validateUser(requestBody.Token) {
c.JSON(http.StatusBadRequest, GeneralError{fmt.Sprintf("invalid token for user %s", requestBody.Name)})
return
}
var user models.User
if err := usersHandler.DB.Preload("Token").First(&user, "name = ?", requestBody.Name).Error; err != nil {
if err == gorm.ErrRecordNotFound {
user = models.User{
Name: requestBody.Name,
ULID: NewULID(),
GithubID: requestBody.GithubID,
GithubUsername: requestBody.GithubUsername,
GithubAvatarURL: requestBody.GithubAvatarURL,
Email: requestBody.Email,
Token: models.Token{
Token: requestBody.Token,
ExpiresAt: requestBody.ExpiresAt,
},
}
} else {
logrus.Error(err)
sentry.CaptureException(err)
c.JSON(http.StatusInternalServerError, GeneralError{"internal error, please try it later"})
return
}
} else {
user.Token.Token = requestBody.Token
user.Token.ExpiresAt = requestBody.ExpiresAt
}
if err := usersHandler.DB.Save(&user).Error; err != nil {
logrus.Error(err)
sentry.CaptureException(err)
c.JSON(http.StatusInternalServerError, GeneralError{"internal error, please try it later"})
return
}
c.JSON(http.StatusOK, UserInfo{ID: user.ID, GithubID: user.GithubID})
}
func validateUser(token string) bool {
client := &http.Client{
Timeout: time.Second * githubHTTPTimeout,
}
req, err := http.NewRequest("GET", "https://api.github.com/user", nil)
if err != nil {
logrus.Error(err)
sentry.CaptureException(err)
return false
}
req.Header.Add("Accept", "application/vnd.github.v3+json")
req.Header.Add("Authorization", fmt.Sprintf("token %s", token))
resp, err := client.Do(req)
if err != nil {
logrus.Error(err)
sentry.CaptureException(err)
return false
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
logrus.Error(err)
sentry.CaptureException(err)
return false
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
log.Warnf("request with error %s", body)
return false
}
logrus.Info()
return true
}
+4 -1
View File
@@ -55,7 +55,7 @@ func main() {
defer db.Close()
// Migrate the schema
db.AutoMigrate(&models.Badge{})
db.AutoMigrate(&models.Badge{}, &models.Token{}, &models.User{})
client, err := api.NewClient(api.Config{
Address: "https://prometheus-api.home.toozhao.com",
@@ -68,6 +68,8 @@ func main() {
handler := &handlers.BadgesHandler{DB: db, PrometheusAPIClient: client}
userHandler := &handlers.UsersHandler{DB: db}
r := gin.Default()
r.Use(sentrygin.New(sentrygin.Options{
@@ -84,6 +86,7 @@ func main() {
api := r.Group("/api")
api.POST("/auth", userHandler.Authenticate)
api.POST("/badges", handler.CreateBadge)
go func() {
+2
View File
@@ -10,6 +10,8 @@ type Badge struct {
Label string
ULID string `gorm:"column:ulid;type:varchar;not null;unique_index"`
Type string
User User
UserID int
Options *postgres.Jsonb
OptionStruct interface{} `gorm:"-"`
}
+22
View File
@@ -0,0 +1,22 @@
package models
import "github.com/jinzhu/gorm"
type User struct {
gorm.Model
Name string
ULID string `gorm:"column:ulid"`
GithubID string
GithubUsername string
GithubAvatarURL string
Email string
Token Token
TokenID string
}
type Token struct {
gorm.Model
Token string
ExpiresAt string
UserID int
}