backend/auth/auth.go
ACoolName f1919d0602
Some checks failed
Build and Push Docker Image / Build image (push) Has been cancelled
fix signup logic
2025-04-09 03:59:32 +03:00

231 lines
5.5 KiB
Go

package auth
import (
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"strings"
"time"
"git.acooldomain.co/server-manager/backend/dbhandler"
"git.acooldomain.co/server-manager/backend/factories"
"git.acooldomain.co/server-manager/backend/models"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt"
)
type AuthApi struct {
config models.GlobalConfig
tokenHandler dbhandler.InviteTokenDbHandler
userAuthDbHandler dbhandler.UserPassAuthanticationDbHandler
serverAuthDbHandler dbhandler.ServersAuthorizationDbHandler
OidcAuthDbHandler dbhandler.OidcAuthenticationDbHandler
}
type Claims struct {
Username string `json:"username"`
Email string `json:"email"`
Permissions models.Permission `json:"permissions"`
}
type AuthClaims struct {
*jwt.StandardClaims
Claims
}
func (con *AuthApi) signToken(token Claims) (string, error) {
t := jwt.New(jwt.GetSigningMethod(con.config.Signing.Algorithm))
t.Claims = &AuthClaims{
&jwt.StandardClaims{
ExpiresAt: time.Now().Add(time.Hour * 24 * 30).Unix(),
},
token,
}
return t.SignedString([]byte(con.config.Signing.Key))
}
func (con *AuthApi) LoggedIn(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) (any, error) {
// Don't forget to validate the alg is what you expect:
if token.Method.Alg() != con.config.Signing.Algorithm {
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 []byte(con.config.Signing.Key), nil
})
if err != nil {
ctx.AbortWithError(403, err)
return
}
if !token.Valid {
ctx.AbortWithStatus(403)
return
}
claims, ok := token.Claims.(*AuthClaims)
if !ok {
ctx.AbortWithStatus(500)
return
}
ctx.Set("claims", claims)
}
type SignUpRequest struct {
Username string
Password string
}
func (con AuthApi) signUp(ctx *gin.Context) {
var request SignUpRequest
rawToken := ctx.Query("token")
err := json.NewDecoder(ctx.Request.Body).Decode(&request)
if err != nil {
ctx.AbortWithError(500, err)
return
}
token, err := con.tokenHandler.GetInviteToken(ctx, rawToken)
if err != nil {
ctx.AbortWithError(500, err)
return
}
if token.Token == "" {
ctx.AbortWithStatusJSON(403, "PermissionDenied")
return
}
err = con.userAuthDbHandler.CreateUser(ctx, request.Username, request.Password, token.Permissions, token.Email, con.config.Users.DefaultMaxOwnedServers)
if err != nil {
ctx.AbortWithError(500, err)
return
}
con.signIn(ctx)
}
type SignInRequest struct {
Username string
Password string
}
func (con AuthApi) signIn(ctx *gin.Context) {
var request SignInRequest
err := json.NewDecoder(ctx.Request.Body).Decode(&request)
if err != nil {
ctx.AbortWithError(500, err)
return
}
userItem, err := con.userAuthDbHandler.AuthenticateUser(ctx, request.Username, request.Password)
if err != nil {
println("handler")
ctx.AbortWithError(403, err)
return
}
token := Claims{
Username: userItem.Username,
Permissions: userItem.Permissions,
}
signedToken, err := con.signToken(token)
if err != nil {
ctx.AbortWithError(500, err)
return
}
ctx.SetCookie("auth", signedToken, int(time.Hour)*24*30, "", "."+con.config.Domain, false, false)
ctx.IndentedJSON(http.StatusOK, signedToken)
}
func (con AuthApi) Verify(ctx *gin.Context) {
claimsPointer, exists := ctx.Get("claims")
if !exists {
ctx.Status(403)
ctx.Error(errors.New("failed to get claims, not logged in"))
return
}
claims, ok := claimsPointer.(*AuthClaims)
if !ok {
ctx.Error(errors.New("failed to convert claims"))
ctx.Status(500)
return
}
forwardedUri := ctx.Request.Header.Get("x-forwarded-uri")
pathSegments := strings.Split(forwardedUri, "/")
serverId, service := pathSegments[2], pathSegments[1]
switch service {
case "browsers":
fmt.Printf("%#v %s", claims, serverId)
serverPermissions, err := con.serverAuthDbHandler.GetPermissions(ctx, claims.Username, serverId)
if err != nil {
ctx.AbortWithError(500, err)
return
}
if (claims.Permissions|serverPermissions)&models.Admin == models.Admin {
ctx.Header("X-Auth-Username", claims.Username)
log.Printf("Set header X-Username %s", claims.Username)
ctx.Status(200)
return
}
case "cloud":
if claims.Permissions&models.Cloud == models.Cloud || claims.Permissions&models.Admin == models.Admin {
log.Printf("Set header X-Username %s", claims.Username)
ctx.Header("X-Auth-Username", claims.Username)
ctx.Status(200)
return
}
}
ctx.Redirect(303, fmt.Sprintf("http://%s/login", con.config.Domain))
}
func LoadGroup(group *gin.RouterGroup, config models.GlobalConfig) gin.HandlerFunc {
userAuthHandler, err := factories.GetUserPassAuthDbHandler(config.Authentication.UserPass)
if err != nil {
panic(err)
}
serverAuthDbHandler, err := factories.GetServersAuthorizationDbHandler(config.ServersAuthorizationDatabase)
if err != nil {
panic(err)
}
inviteHandler, err := factories.GetInviteTokenDbHandler(config.Authentication.UserPass.InviteTokenDatabase)
if err != nil {
panic(err)
}
connection := AuthApi{
userAuthDbHandler: userAuthHandler,
serverAuthDbHandler: serverAuthDbHandler,
tokenHandler: inviteHandler,
config: config,
}
group.POST("/signin", connection.signIn)
group.POST("/signup", connection.signUp)
group.Any("/verify", connection.LoggedIn, connection.Verify)
return connection.LoggedIn
}