package auth import ( "context" "encoding/json" "fmt" "net/http" "strings" "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 var DOMAIN string type Connection struct { DatabaseConnection *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.DatabaseConnection.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.DatabaseConnection.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.DatabaseConnection.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, "", ".games.acooldomain.co", true, true) c.IndentedJSON(http.StatusOK, signedToken) } func (con Connection) verify(c *gin.Context) { claims, exists := c.Get("claims") if !exists { fmt.Println("No Claims") c.AbortWithStatus(403) return } forwarded_host := c.Request.Header.Get("x-forwarded-host") domainSegments := strings.Split(forwarded_host, ".") serverId, service := domainSegments[0], domainSegments[1] c.AddParam("server_id", serverId) if service == "browsers" { if claims.(*AuthClaims).Permissions&models.Browse == models.Browse || claims.(*AuthClaims).Permissions&models.Admin == models.Admin || con.ServerAuthorized(models.Browse)(c) { c.Header("X-Username", claims.(*AuthClaims).Username) c.Status(200) return } } if service == "cloud" { if claims.(*AuthClaims).Permissions&models.Cloud == models.Cloud || claims.(*AuthClaims).Permissions&models.Admin == models.Admin { c.Header("X-Username", claims.(*AuthClaims).Username) c.Status(200) return } } c.Redirect(303, fmt.Sprintf("http://%s/login", DOMAIN)) } func (con Connection) ServerAuthorized(permissions models.Permission) func(*gin.Context) bool { return func(ctx *gin.Context) bool { claims, exists := ctx.Get("claims") if !exists { return false } server_id := ctx.Param("server_id") if server_id == "" { return false } var serverData models.ServerData con.DatabaseConnection.Database("Backend").Collection("Servers").FindOne(context.TODO(), bson.D{{Key: "Id", Value: server_id}}).Decode(&serverData) if serverData.OwnerId == claims.(*AuthClaims).Username { return true } userPermissions := serverData.UserPermissions[claims.(*AuthClaims).Username] if userPermissions&permissions == permissions || userPermissions&models.Admin == models.Admin { return true } return false } } func LoadGroup(group *gin.RouterGroup, client *mongo.Client, config models.GlobalConfig) { connection := Connection{DatabaseConnection: client} secret = []byte(config.Key) method = config.Algorithm DOMAIN = config.Domain group.POST("/signin", connection.signIn) group.POST("/signup", AuthorizedTo(models.Admin), connection.signUp) group.GET("/verify", AuthorizedTo(0), connection.verify) }