From 3afd7c2edab281db6e098e4267f47f2ce3dc4aeb Mon Sep 17 00:00:00 2001 From: ACoolName Date: Sat, 25 May 2024 16:47:38 +0000 Subject: [PATCH] Update auth.go --- auth/auth.go | 548 +++++++++++++++++++++++++-------------------------- 1 file changed, 274 insertions(+), 274 deletions(-) diff --git a/auth/auth.go b/auth/auth.go index 2ecf8bf..7ab2379 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -1,274 +1,274 @@ -package auth - -import ( - "context" - "encoding/json" - "fmt" - "log" - "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, int(time.Hour)*24*30, "", ".games.acooldomain.co", true, false) - 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") - log.Printf("Checking auth of %s", 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) - log.Printf("Set header X-Username %s", 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 { - log.Printf("Set header X-Username %s", claims.(*AuthClaims).Username) - 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.Any("/verify", AuthorizedTo(0), connection.verify) -} +package auth + +import ( + "context" + "encoding/json" + "fmt" + "log" + "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, int(time.Hour)*24*30, "", ".games.acooldomain.co", true, false) + c.IndentedJSON(http.StatusOK, signedToken) +} + +func (con Connection) verify(c *gin.Context) { + claims, exists := c.Get("claims") + if !exists { + log.Println("No Claims") + c.AbortWithStatus(403) + return + } + + forwarded_host := c.Request.Header.Get("x-forwarded-host") + log.Printf("Checking auth of %s", 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) + log.Printf("Set header X-Username %s", 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 { + log.Printf("Set header X-Username %s", claims.(*AuthClaims).Username) + 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.Any("/verify", AuthorizedTo(0), connection.verify) +}