TP-61307 | Auth middileware and relase service changes

This commit is contained in:
Lokesh Dugar
2024-08-27 13:40:31 +05:30
parent 5ac5f89134
commit f537e30cac
13 changed files with 237 additions and 22 deletions

View File

@@ -73,8 +73,11 @@ elastic:
index: cybertron
api_key: U3NUSmFKRUJYaHF5bTJkOUozUU06Z1ZDTE9hZm1RUnlXeHRNY21yeGxfQQ==
aws:
region: ap-south-1
bucket: navi-cd955a63c4476df0f00c1cea0e4a40d1
#mjolnir config
mjolnir:
service.url: https://qa-mjolnir-service.np.navi-ppl.in
realm.id: O3G7sCWk4r

View File

@@ -21,6 +21,7 @@ type AppConfig struct {
awsConfig *AwsConfig
KafkaConfig *KafkaConfig
ElasticConfig *ElasticConfig
mjolnir *MjolnirClientConfig
}
type MigConfig struct {
@@ -47,6 +48,7 @@ func LoadConfig() {
awsConfig: NewAWSConfig(),
KafkaConfig: NewKafkaConfig(),
ElasticConfig: NewElasticConfig(),
mjolnir: NewMjolnirConfig(),
}
}
@@ -112,3 +114,7 @@ func GetKafkaConfig() *KafkaConfig {
func GetElasticConfig() *ElasticConfig {
return appConfig.ElasticConfig
}
func GetMjolnirConfig() *MjolnirClientConfig {
return appConfig.mjolnir
}

22
configs/mjolnir_config.go Normal file
View File

@@ -0,0 +1,22 @@
package configs
type MjolnirClientConfig struct {
baseUrl string
realmId string
}
func NewMjolnirConfig() *MjolnirClientConfig {
return &MjolnirClientConfig{
baseUrl: getString("mjolnir.service.url", true),
realmId: getString("mjolnir.realm.id", true),
}
}
func (p *MjolnirClientConfig) GetMjolnirBaseUrl() string {
return p.baseUrl
}
func (p *MjolnirClientConfig) GetMjolnirRealmId() string {
return p.realmId
}

6
constants/common.go Normal file
View File

@@ -0,0 +1,6 @@
package constants
const (
SESSION_HEADER_NAME = "x-session-token"
EMAIL_HEADER_NAME = "x-user-email"
)

View File

@@ -11,6 +11,7 @@ import (
httpclient "cybertron/pkg/httpClient"
"cybertron/pkg/kafka/producer"
"cybertron/pkg/log"
"cybertron/pkg/mjolnirClient"
"cybertron/service"
"go.uber.org/zap"
@@ -31,6 +32,7 @@ type Service struct {
SourceMapService *service.SourceMapService
ReleaseService *service.ReleaseService
ExceptionService *service.ExceptionService
AuthService *service.AuthService
SearchService *service.SearchService
S3Client *aws.Actions
// Add your service here
@@ -58,6 +60,7 @@ func InitDependencies() *Dependencies {
s3Client := aws.NewS3client(*configs.GetAWSConfig())
kafkaProducer := initKafkaProducer()
elasticSearch, _ := elastic.NewElasticClient(*configs.GetElasticConfig())
mjolnirClient := mjolnirClient.NewMjolnirClient(*configs.GetMjolnirConfig())
documentServiceClient := document.NewDocumentServiceHttpClient(httpClient, logger, configs.GetDocumentServiceHttpClientConfigs())
projectServiceClient := service.NewProjectCreator(logger, dbClient, s3Client, kafkaProducer)
@@ -65,9 +68,11 @@ func InitDependencies() *Dependencies {
releaseServiceClient := service.NewReleaseService(logger, dbClient)
exceptionServiceClient := service.NewExceptionService(logger, dbClient, kafkaProducer)
searchServiceClient := service.NewSearchService(logger, elasticSearch)
authService := service.NewAuthService(mjolnirClient)
services := initServices(documentServiceClient, projectServiceClient, sourceMapServiceClient, releaseServiceClient, exceptionServiceClient, searchServiceClient)
services := initServices(documentServiceClient, projectServiceClient, sourceMapServiceClient, releaseServiceClient, exceptionServiceClient, searchServiceClient, authService)
handlers := initHandlers(projectServiceClient, sourceMapServiceClient, releaseServiceClient, exceptionServiceClient, searchServiceClient)
return &Dependencies{
Service: services,
DBClient: dbClient,
@@ -77,7 +82,7 @@ func InitDependencies() *Dependencies {
}
}
func initServices(documentService *document.HttpClient, projectService *service.ProjectCreator, sourceMapService *service.SourceMapService, releaseService *service.ReleaseService, exceptionService *service.ExceptionService, searchService *service.SearchService) *Service {
func initServices(documentService *document.HttpClient, projectService *service.ProjectCreator, sourceMapService *service.SourceMapService, releaseService *service.ReleaseService, exceptionService *service.ExceptionService, searchService *service.SearchService, authService *service.AuthService) *Service {
return &Service{
DocumentService: documentService,
ProjectService: projectService,
@@ -85,6 +90,7 @@ func initServices(documentService *document.HttpClient, projectService *service.
ReleaseService: releaseService,
ExceptionService: exceptionService,
SearchService: searchService,
AuthService: authService,
}
}

View File

@@ -0,0 +1,52 @@
package middleware
import (
"cybertron/constants"
"cybertron/service"
"net/http"
"github.com/gin-gonic/gin"
)
type UserInfo struct {
SessionToken string `json:"sessionToken"`
ClientID string `json:"clientId"`
Name string `json:"name"`
Exp int `json:"exp"`
EmailID string `json:"emailId"`
AccountID string `json:"accountId"`
PhoneNumber string `json:"phoneNumber"`
Roles []string `json:"roles"`
Groups []string `json:"groups"`
Permissions []string `json:"permissions"`
FirebaseJwtToken string `json:"firebaseJwtToken"`
FirebaseNode string `json:"firebaseNode"`
ProfilePictureURL string `json:"profilePictureUrl"`
PreferredUsername string `json:"preferred_username"`
}
func PermissionMiddleware(authService *service.AuthService) gin.HandlerFunc {
return func(c *gin.Context) {
sessionToken := c.GetHeader(constants.SESSION_HEADER_NAME)
userEmail := c.GetHeader(constants.EMAIL_HEADER_NAME)
validUser, err := authService.CheckValidUser(c, sessionToken, userEmail)
if err != nil || !validUser {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
c.Abort()
return
}
c.Next()
}
}
func isAdmin(roles []string) bool {
for _, role := range roles {
if role == "Admin" {
return true
}
}
return false
}

View File

@@ -1,6 +1,7 @@
package transport
import (
"cybertron/internal/transport/middleware"
"cybertron/internal/transport/router"
"fmt"
"github.com/gin-contrib/cors"
@@ -48,6 +49,7 @@ func (s *Server) Start() {
AllowCredentials: true,
MaxAge: 24 * time.Hour,
}))
s.gin.Use(middleware.PermissionMiddleware(s.dependencies.Service.AuthService))
s.router()
port := configs.GetPort()

View File

@@ -5,7 +5,7 @@ import (
)
type Project struct {
ID int `json:"id"`
ID int `json:"id" gorm:"autoIncrement:true"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
DeletedAt time.Time `json:"deletedAt"`

View File

@@ -1,9 +1,14 @@
package db
import "gorm.io/gorm"
import (
"time"
)
type Release struct {
gorm.Model
ProjectReferenceId string `gorm:"primaryKey"`
ReleaseVersion string `gorm:"column:name"`
ID int64 `json:"id" gorm:"autoIncrement:true"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
DeletedAt time.Time `json:"deletedAt"`
ProjectReferenceId string `json:"projectReferenceId" gorm:"primaryKey"`
ReleaseVersion string `json:"releaseVersion" gorm:"primaryKey"`
}

View File

@@ -0,0 +1,78 @@
package mjolnirClient
import (
"cybertron/configs"
"cybertron/pkg/log"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
)
type MjolnirClient struct {
baseUrl string
realmId string
}
type MjolnirError struct {
Code string `json:"code,omitempty"`
Message string `json:"message,omitempty"`
Params interface{} `json:"params,omitempty"`
}
type MjolnirSessionResponse struct {
SessionToken string `json:"sessionToken,omitempty"`
ClientId string `json:"clientId,omitempty"`
EmailId string `json:"emailId,omitempty"`
AccountId string `json:"accountId,omitempty"`
PhoneNumber string `json:"phoneNumber,omitempty"`
PreferredUsername string `json:"preferred_username,omitempty"`
Roles []string `json:"roles,omitempty"`
Groups []string `json:"groups,omitempty"`
Permissions []string `json:"permissions,omitempty"`
StatusCode int `json:"statusCode,omitempty"`
Errors []MjolnirError `json:"errors"`
}
func NewMjolnirClient(mjolnirConfig configs.MjolnirClientConfig) *MjolnirClient {
return &MjolnirClient{
baseUrl: mjolnirConfig.GetMjolnirBaseUrl(),
realmId: mjolnirConfig.GetMjolnirRealmId(),
}
}
var logger = log.Log.GetLog()
var client = http.Client{}
const (
SessionUrl = "%s/session/%s"
)
func (m *MjolnirClient) GetSessionResponse(sessionToken string) (*MjolnirSessionResponse, error) {
if sessionToken == "null" {
return nil, errors.New("unauthorized request")
}
req, _ := http.NewRequest("GET", fmt.Sprintf(SessionUrl, m.baseUrl, m.realmId), nil)
req.Header.Add("X-Session-Token", sessionToken)
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
responseBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var response MjolnirSessionResponse
err = json.Unmarshal(responseBody, &response)
if err != nil {
return nil, err
}
logger.Info(fmt.Sprintf("%v", response))
return &response, nil
}

34
service/AuthService.go Normal file
View File

@@ -0,0 +1,34 @@
package service
import (
"cybertron/pkg/mjolnirClient"
"fmt"
"github.com/gin-gonic/gin"
"net/http"
)
type AuthService struct {
mjolnirClient *mjolnirClient.MjolnirClient
}
func NewAuthService(mjolnirClient *mjolnirClient.MjolnirClient) *AuthService {
return &AuthService{
mjolnirClient: mjolnirClient,
}
}
type UserRoles string
func (authService *AuthService) CheckValidUser(c *gin.Context, sessionToken, userEmail string) (bool, error) {
sessionResponse, err := authService.mjolnirClient.GetSessionResponse(sessionToken)
if err != nil || sessionResponse.StatusCode == http.StatusUnauthorized {
return false, err
}
if sessionResponse.EmailId != userEmail {
return false, fmt.Errorf("user email: %v does not match the email linked to the session token", userEmail)
}
c.Set("permissions", sessionResponse.Permissions)
return true, nil
}

View File

@@ -14,9 +14,8 @@ type ReleaseService struct {
}
type ReleaseBody struct {
ProjectId string `json:"project_id" binding:"required"`
Version string `json:"version" binding:"required"`
SourceMapUrl string `json:"source_map_url" binding:"required"`
ProjectReferenceId string `json:"projectReferenceId" binding:"required"`
ReleaseVersion string `json:"releaseVersion" binding:"required"`
}
func NewReleaseService(logger *log.Logger, dbClient *gorm.DB) *ReleaseService {
@@ -26,7 +25,6 @@ func NewReleaseService(logger *log.Logger, dbClient *gorm.DB) *ReleaseService {
}
}
// FIXME: This may not be requried now, can be used in source maps services only
func (releaseService *ReleaseService) AddRelease(c *gin.Context) {
var releaseBody ReleaseBody
if err := c.BindJSON(&releaseBody); err != nil {
@@ -37,8 +35,8 @@ func (releaseService *ReleaseService) AddRelease(c *gin.Context) {
}
releaseToBeAdded := db.Release{
ProjectReferenceId: releaseBody.ProjectId,
ReleaseVersion: releaseBody.Version,
ProjectReferenceId: releaseBody.ProjectReferenceId,
ReleaseVersion: releaseBody.ReleaseVersion,
}
if result := releaseService.dbClient.Create(&releaseToBeAdded); result.Error != nil {
@@ -53,11 +51,12 @@ func (releaseService *ReleaseService) AddRelease(c *gin.Context) {
func (releaseService *ReleaseService) GetReleases(c *gin.Context) {
projectRefId := c.Query("project_reference_id")
releaseVersion := c.Query("release_version")
var releases []db.Release
releaseService.dbClient.Where("project_reference_id = ?", projectRefId).Find(&releases)
result := releaseService.dbClient.Where("project_reference_id = ? OR release_version = ?", projectRefId, releaseVersion).Find(&releases)
if result := releaseService.dbClient.Find(&releases); result.Error != nil {
if result.Error != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": result.Error.Error()})
return
}

View File

@@ -21,9 +21,9 @@ type SourceMapService struct {
}
type SourceMapAckBody struct {
ProjectId string `json:"project-id" binding:"required"`
ReleaseId string `json:"releaseId" binding:"required"`
FileName string `json:"file_name" binding:"required"`
ProjectReferenceId string `json:"projectId" binding:"required"`
ReleaseId string `json:"releaseId" binding:"required"`
FileName string `json:"fileName" binding:"required"`
}
func NewSourceMapService(dbClient *gorm.DB, s3Client *aws.Actions, config *configs.AwsConfig) *SourceMapService {
@@ -45,10 +45,11 @@ func (s *SourceMapService) GetSourceMapUploadUrl(ctx *gin.Context) {
})
return
}
//generate s3 pre-signed url
key := path.Join(projectId, releaseId, fileName)
bucket := s.awsConfig.Bucket
request, err := s.s3Client.S3PresignClient.PresignGetObject(context.TODO(), &s3.GetObjectInput{
request, err := s.s3Client.S3PresignClient.PresignPutObject(context.TODO(), &s3.PutObjectInput{
Bucket: &bucket,
Key: &key,
}, func(opts *s3.PresignOptions) {
@@ -83,9 +84,10 @@ func (s *SourceMapService) SourceMapUploadAck(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
//find the record to update ack
var existingSourceMap db.SourceMap
existingRecordError := s.dbClient.First(&existingSourceMap, "project_reference_id = ? and release_reference_id= ? and file_name = ?", sourceMapAckBody.ProjectId, sourceMapAckBody.ReleaseId, sourceMapAckBody.FileName).Error
existingRecordError := s.dbClient.First(&existingSourceMap, "project_reference_id = ? and release_reference_id= ? and file_name = ?", sourceMapAckBody.ProjectReferenceId, sourceMapAckBody.ReleaseId, sourceMapAckBody.FileName).Error
if existingRecordError != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": existingRecordError.Error()})
return