package auth import ( "context" "encoding/json" "fmt" "net/http" "time" "acooldomain.co/backend/models" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "golang.org/x/crypto/bcrypt" ) var secret []byte var method string type Connection struct { connection *mongo.Client } type TokenInfo struct { Username string `json:"username"` Permissions models.Permission `json:"permissions"` } type AuthClaims struct { *jwt.StandardClaims TokenInfo } type InviteToken struct { Email string `bson:"Email"` Permissions models.Permission `bson:"Permissions"` Token string `bson:"Token"` } func signToken(token TokenInfo) (string, error) { t := jwt.New(jwt.GetSigningMethod(method)) t.Claims = &AuthClaims{ &jwt.StandardClaims{ ExpiresAt: time.Now().Add(time.Hour * 24 * 30).Unix(), }, token, } return t.SignedString(secret) } func hashPassword(password string) (string, error) { bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) return string(bytes), err } func AuthorizedTo(requiredPermissions models.Permission, overwriters ...func(*gin.Context) bool) gin.HandlerFunc { return func(ctx *gin.Context) { authCookie, err := ctx.Request.Cookie("auth") if err != nil { ctx.AbortWithError(403, err) return } token, err := jwt.ParseWithClaims(authCookie.Value, &AuthClaims{}, func(token *jwt.Token) (interface{}, error) { // Don't forget to validate the alg is what you expect: if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } // hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key") return secret, nil }) if err != nil { ctx.AbortWithError(403, err) return } if claims, ok := token.Claims.(*AuthClaims); ok && token.Valid { ctx.Set("claims", claims) if (requiredPermissions&claims.Permissions != requiredPermissions) && (models.Admin&claims.Permissions != models.Admin) { for _, overwrite := range overwriters { if overwrite(ctx) { return } } ctx.AbortWithStatus(403) return } } else { ctx.AbortWithStatus(500) } } } type SignUpRequest struct { Token string Username string Password string } func (con Connection) signUp(c *gin.Context) { var request SignUpRequest err := json.NewDecoder(c.Request.Body).Decode(&request) if err != nil { c.AbortWithError(500, err) } var token InviteToken err = con.connection.Database("Backend").Collection("Tokens").FindOne( context.TODO(), bson.D{{}}, options.FindOne(), ).Decode(&token) if err != nil { c.AbortWithError(500, err) return } if token.Token == "" { c.AbortWithStatusJSON(403, "PermissionDenied") return } hashedPass, err := hashPassword(request.Password) if err != nil { c.AbortWithError(500, err) return } _, err = con.connection.Database("Backend").Collection("Users").InsertOne(context.TODO(), &models.User{ Username: request.Username, HashedPass: hashedPass, Permissions: token.Permissions, MaxOwnedServers: 5, Email: token.Email, }, &options.InsertOneOptions{}) if err != nil { c.AbortWithError(500, err) return } con.signIn(c) } type SignInRequest struct { Username string Password string } func (con Connection) signIn(c *gin.Context) { var request SignInRequest err := json.NewDecoder(c.Request.Body).Decode(&request) if err != nil { c.AbortWithError(500, err) return } var userItem models.User err = con.connection.Database("Backend").Collection("Users").FindOne(context.TODO(), bson.D{{Key: "Username", Value: request.Username}}).Decode(&userItem) if err != nil { c.AbortWithError(403, err) return } if bcrypt.CompareHashAndPassword([]byte(userItem.HashedPass), []byte(request.Password)) != nil { c.AbortWithStatus(403) return } token := TokenInfo{ Username: userItem.Username, Permissions: userItem.Permissions, } signedToken, err := signToken(token) if err != nil { c.AbortWithError(500, err) return } c.SetCookie("auth", signedToken, -1, "", "", false, true) c.IndentedJSON(http.StatusOK, signedToken) } func (con Connection) test(c *gin.Context) { claims, exists := c.Get("claims") if !exists { fmt.Println("No Claims") c.AbortWithStatus(403) return } c.IndentedJSON(http.StatusOK, claims) } func LoadGroup(group *gin.RouterGroup, client *mongo.Client, config models.GlobalConfig) { connection := Connection{connection: client} secret = []byte(config.Key) method = config.Algorithm group.POST("/signin", connection.signIn) group.POST("/signup", AuthorizedTo(models.Admin), connection.signUp) group.GET("/test", AuthorizedTo(models.Admin), connection.test) }