package services import ( "encoding/base64" "net/http" "strings" "time" "git.secnex.io/secnex/masterlog" "git.secnex.io/secnex/oauth2-api/config" "git.secnex.io/secnex/oauth2-api/models" "git.secnex.io/secnex/oauth2-api/repositories" "git.secnex.io/secnex/oauth2-api/utils" "github.com/gofiber/fiber/v2" "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" ) type TokenResponse struct { TokenType string `json:"token_type"` Scope string `json:"scope"` ExpiresIn int `json:"expires_in"` ExtExpiresIn int `json:"ext_expires_in"` AccessToken string `json:"access_token"` RefreshToken string `json:"refresh_token"` } func Token(clientID, grantType, code, redirectURI, clientSecret string) *utils.HTTPResponse { now := time.Now().UTC() application, err := repositories.GetApplicationByID(clientID) if err != nil { masterlog.Debug("Application not found", map[string]interface{}{"error": err.Error(), "client_id": clientID}) return utils.NewHTTPResponse(http.StatusUnauthorized, &fiber.Map{"error": "Application not found"}, "", nil, nil) } if application.ExpiresAt.Before(time.Now().UTC()) { masterlog.Debug("Application expired", map[string]interface{}{"client_id": clientID}) return utils.NewHTTPResponse(http.StatusUnauthorized, &fiber.Map{"error": "Application expired"}, "", nil, nil) } valid, err := utils.Verify(clientSecret, application.Secret) if err != nil { masterlog.Debug("Invalid client secret", map[string]interface{}{"error": err.Error(), "client_id": clientID}) return utils.NewHTTPResponse(http.StatusUnauthorized, &fiber.Map{"error": "Invalid client secret"}, "", nil, nil) } if !valid { masterlog.Debug("Invalid client secret", map[string]interface{}{"client_id": clientID}) return utils.NewHTTPResponse(http.StatusUnauthorized, &fiber.Map{"error": "Invalid client secret"}, "", nil, nil) } authorizationCodePlain, err := base64.StdEncoding.DecodeString(code) if err != nil { masterlog.Debug("Invalid authorization code", map[string]interface{}{"error": err.Error(), "client_id": clientID}) return utils.NewHTTPResponse(http.StatusUnauthorized, &fiber.Map{"error": "Invalid authorization code"}, "", nil, nil) } authorizationCodeParts := strings.Split(string(authorizationCodePlain), ":") if len(authorizationCodeParts) != 2 { masterlog.Debug("Invalid authorization code", map[string]interface{}{"client_id": clientID}) return utils.NewHTTPResponse(http.StatusUnauthorized, &fiber.Map{"error": "Invalid authorization code"}, "", nil, nil) } authorizationID := uuid.MustParse(authorizationCodeParts[0]) authorizationCode := authorizationCodeParts[1] authorization, err := repositories.GetAuthorizationByID(authorizationID.String()) if err != nil { masterlog.Debug("Authorization not found", map[string]interface{}{"error": err.Error(), "client_id": clientID}) return utils.NewHTTPResponse(http.StatusUnauthorized, &fiber.Map{"error": "Authorization not found"}, "", nil, nil) } codeValid, err := utils.Verify(authorizationCode, authorization.Code) if err != nil { masterlog.Debug("Invalid authorization code", map[string]interface{}{"error": err.Error(), "client_id": clientID}) return utils.NewHTTPResponse(http.StatusUnauthorized, &fiber.Map{"error": "Invalid authorization code"}, "", nil, nil) } if !codeValid { masterlog.Debug("Invalid authorization code", map[string]interface{}{"client_id": clientID}) return utils.NewHTTPResponse(http.StatusUnauthorized, &fiber.Map{"error": "Invalid authorization code"}, "", nil, nil) } tokenID := uuid.New() refreshToken := utils.GenerateRandomString(64) token := &models.Token{ ID: tokenID, RefreshToken: refreshToken, UserID: authorization.UserID, } if err := repositories.CreateToken(token); err != nil { masterlog.Debug("Failed to create token", map[string]interface{}{"error": err.Error(), "client_id": clientID}) return utils.NewHTTPResponse(http.StatusInternalServerError, &fiber.Map{"error": "Failed to create token"}, "", nil, nil) } if err := repositories.ExpireAuthorization(authorizationID.String()); err != nil { masterlog.Debug("Failed to expire authorization", map[string]interface{}{"error": err.Error(), "client_id": clientID}) return utils.NewHTTPResponse(http.StatusInternalServerError, &fiber.Map{"error": "Failed to expire authorization"}, "", nil, nil) } user, err := repositories.GetUserByID(authorization.UserID.String()) if err != nil { masterlog.Debug("Failed to get user", map[string]interface{}{"error": err.Error(), "client_id": clientID}) return utils.NewHTTPResponse(http.StatusInternalServerError, &fiber.Map{"error": "Failed to get user"}, "", nil, nil) } userClaims := map[string]interface{}{ "id": user.ID.String(), "username": user.Username, "email": user.Email, "first_name": user.FirstName, "last_name": user.LastName, } if user.TenantID != nil { userClaims["tenant_id"] = user.TenantID.String() } if user.ExternalID != nil { userClaims["external_id"] = user.ExternalID.String() } accessToken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "sub": tokenID.String(), "exp": token.SessionExpiresAt.Unix(), "iat": now.Unix(), "aud": clientID, "iss": "https://secnex.io", "user": userClaims, }) secret := config.CONFIG.JwtSecret accessTokenString, err := accessToken.SignedString([]byte(secret)) if err != nil { masterlog.Debug("Failed to sign access token", map[string]interface{}{"error": err.Error(), "client_id": clientID}) return utils.NewHTTPResponse(http.StatusInternalServerError, &fiber.Map{"error": "Failed to sign access token"}, "", nil, nil) } tokenExpiresAt := token.SessionExpiresAt.Unix() extTokenExpiresAt := token.RefreshTokenExpiresAt.Unix() response := TokenResponse{ TokenType: "Bearer", Scope: "", ExpiresIn: int(tokenExpiresAt - now.Unix()), ExtExpiresIn: int(extTokenExpiresAt - now.Unix()), AccessToken: accessTokenString, RefreshToken: refreshToken, } masterlog.Debug("Token created successfully", map[string]interface{}{"client_id": clientID}) return utils.NewHTTPResponse(http.StatusOK, &fiber.Map{"response": response}, "", nil, nil) }