Skip to content

Commit

Permalink
feat: Added enhancement in authentication server (litmuschaos#4024)
Browse files Browse the repository at this point in the history
* base commit to add dockerfile

Signed-off-by: Sarthak Jain <sarthak.jain@harness.io>

* Added  utility functions

Signed-off-by: Sarthak Jain <sarthak.jain@harness.io>

* Added schemas

Signed-off-by: Sarthak Jain <sarthak.jain@harness.io>

* Added presenters

Signed-off-by: Sarthak Jain <sarthak.jain@harness.io>

* Added pkg, handlers and roles

Signed-off-by: Sarthak Jain <sarthak.jain@harness.io>

* Code optimisation

Signed-off-by: Sarthak Jain <sarthak.jain@harness.io>

---------

Signed-off-by: Sarthak Jain <sarthak.jain@harness.io>
Co-authored-by: Saranya Jena <saranya.jena@harness.io>
Signed-off-by: SohamRatnaparkhi <soham.ratnaparkhi@gmail.com>
  • Loading branch information
2 people authored and SohamRatnaparkhi committed Jun 30, 2023
1 parent 3d858e9 commit e151e20
Show file tree
Hide file tree
Showing 42 changed files with 4,638 additions and 0 deletions.
28 changes: 28 additions & 0 deletions chaoscenter/authentication/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# BUILD STAGE
FROM golang:1.18 AS builder

ARG TARGETOS=linux
ARG TARGETARCH

ADD . /auth-server
WORKDIR /auth-server

ENV GOOS=${TARGETOS} \
GOARCH=${TARGETARCH}

RUN go env

RUN CGO_ENABLED=0 go build -o /output/server -v ./api/

# Packaging stage
# Image source: https://github.com/litmuschaos/test-tools/blob/master/custom/hardened-alpine/infra/Dockerfile
# The base image is non-root (have litmus user) with default litmus directory.
FROM litmuschaos/infra-alpine

LABEL maintainer="LitmusChaos"

COPY --from=builder /output/server /litmus

CMD ["./server"]

EXPOSE 3000
103 changes: 103 additions & 0 deletions chaoscenter/authentication/api/handlers/grpc/grpc_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package grpc

import (
"context"
"litmus/litmus-portal/authentication/api/middleware"
"litmus/litmus-portal/authentication/api/presenter/protos"
"litmus/litmus-portal/authentication/pkg/entities"
"litmus/litmus-portal/authentication/pkg/validations"
"strconv"

log "github.com/sirupsen/logrus"

"github.com/golang-jwt/jwt"
)

func (s *ServerGrpc) ValidateRequest(ctx context.Context,
inputRequest *protos.ValidationRequest) (*protos.ValidationResponse, error) {
token, err := middleware.ValidateToken(inputRequest.Jwt)
if err != nil {
return &protos.ValidationResponse{Error: err.Error(), IsValid: false}, err
}
claims := token.Claims.(jwt.MapClaims)
uid := claims["uid"].(string)
err = validations.RbacValidator(uid, inputRequest.ProjectId,
inputRequest.RequiredRoles, inputRequest.Invitation, s.ApplicationService)
if err != nil {
return &protos.ValidationResponse{Error: err.Error(), IsValid: false}, err
}
return &protos.ValidationResponse{Error: "", IsValid: true}, nil
}

func (s *ServerGrpc) GetProjectById(ctx context.Context,
inputRequest *protos.GetProjectByIdRequest) (*protos.GetProjectByIdResponse, error) {

project, err := s.ApplicationService.GetProjectByProjectID(inputRequest.ProjectID)
if err != nil {
log.Error(err)
return nil, err
}

// Fetching user ids of all the members in the project
var uids []string

for _, member := range project.Members {
uids = append(uids, member.UserID)
}

memberMap := make(map[string]entities.User)

authUsers, err := s.ApplicationService.FindUsersByUID(uids)
for _, authUser := range *authUsers {
memberMap[authUser.ID] = authUser
}

var projectMembers []*protos.ProjectMembers

// Adding additional details of project members
for _, member := range project.Members {
var projectMember protos.ProjectMembers
projectMember.Email = memberMap[member.UserID].Email
projectMember.UserName = memberMap[member.UserID].UserName
projectMember.Invitation = string(member.Invitation)
projectMember.Uid = member.UserID
projectMember.JoinedAt = member.JoinedAt
projectMembers = append(projectMembers, &projectMember)
}

if err != nil {
return nil, err
}

return &protos.GetProjectByIdResponse{
Id: project.ID,
Name: project.Name,
Members: projectMembers,
State: "",
CreatedAt: strconv.FormatInt(project.CreatedAt, 10),
UpdatedAt: strconv.FormatInt(project.UpdatedAt, 10),
}, nil
}

func (s *ServerGrpc) GetUserById(ctx context.Context,
inputRequest *protos.GetUserByIdRequest) (*protos.GetUserByIdResponse, error) {
user, err := s.ApplicationService.GetUser(inputRequest.UserID)
if err != nil {
log.Error(err)
return nil, err
}
var deactivatedAt string
if user.DeactivatedAt != nil {
deactivatedAt = strconv.FormatInt(*user.DeactivatedAt, 10)
}
return &protos.GetUserByIdResponse{
Id: user.ID,
Name: user.Name,
Username: user.UserName,
CreatedAt: strconv.FormatInt(user.CreatedAt, 10),
UpdatedAt: strconv.FormatInt(user.UpdatedAt, 10),
DeactivatedAt: deactivatedAt,
Role: string(user.Role),
Email: user.Email,
}, nil
}
11 changes: 11 additions & 0 deletions chaoscenter/authentication/api/handlers/grpc/grpc_server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package grpc

import (
"litmus/litmus-portal/authentication/api/presenter/protos"
"litmus/litmus-portal/authentication/pkg/services"
)

type ServerGrpc struct {
services.ApplicationService
protos.UnimplementedAuthRpcServiceServer
}
126 changes: 126 additions & 0 deletions chaoscenter/authentication/api/handlers/rest/dex_auth_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package rest

import (
"context"
"litmus/litmus-portal/authentication/api/presenter"
"litmus/litmus-portal/authentication/pkg/entities"
"litmus/litmus-portal/authentication/pkg/services"
"litmus/litmus-portal/authentication/pkg/utils"
"net/http"
"time"

"github.com/coreos/go-oidc/v3/oidc"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"golang.org/x/oauth2"
)

func oAuthDexConfig() (*oauth2.Config, *oidc.IDTokenVerifier, error) {
ctx := oidc.ClientContext(context.Background(), &http.Client{})
provider, err := oidc.NewProvider(ctx, utils.DexOIDCIssuer)
if err != nil {
log.Errorf("OAuth Error: Something went wrong with OIDC provider %s", err)
return nil, nil, err
}
return &oauth2.Config{
RedirectURL: utils.DexCallBackURL,
ClientID: utils.DexClientID,
ClientSecret: utils.DexClientSecret,
Scopes: []string{"openid", "profile", "email"},
Endpoint: provider.Endpoint(),
}, provider.Verifier(&oidc.Config{ClientID: utils.DexClientID}), nil
}

// DexLogin handles and redirects to DexServer to proceed with OAuth
func DexLogin() gin.HandlerFunc {
return func(c *gin.Context) {

dexToken, err := utils.GenerateOAuthJWT()
if err != nil {
log.Error(err)
c.JSON(utils.ErrorStatusCodes[utils.ErrServerError], presenter.CreateErrorResponse(utils.ErrServerError))
return
}
config, _, err := oAuthDexConfig()
if err != nil {
log.Error(err)
c.JSON(utils.ErrorStatusCodes[utils.ErrServerError], presenter.CreateErrorResponse(utils.ErrServerError))
return
}
url := config.AuthCodeURL(dexToken)
c.Redirect(http.StatusTemporaryRedirect, url)
}
}

// DexCallback is the handler that creates/logs in the user from Dex and provides JWT to frontend via a redirect
func DexCallback(userService services.ApplicationService) gin.HandlerFunc {
return func(c *gin.Context) {
incomingState := c.Query("state")
validated, err := utils.ValidateOAuthJWT(incomingState)
if !validated {
c.Redirect(http.StatusTemporaryRedirect, "/")
}
config, verifier, err := oAuthDexConfig()
if err != nil {
log.Error(err)
c.JSON(utils.ErrorStatusCodes[utils.ErrServerError], presenter.CreateErrorResponse(utils.ErrServerError))
return
}
token, err := config.Exchange(context.Background(), c.Query("code"))
if err != nil {
log.Error(err)
c.JSON(utils.ErrorStatusCodes[utils.ErrServerError], presenter.CreateErrorResponse(utils.ErrServerError))
return
}

rawIDToken, ok := token.Extra("id_token").(string)
if !ok {
log.Error("OAuth Error: no raw id_token found")
c.JSON(utils.ErrorStatusCodes[utils.ErrServerError], presenter.CreateErrorResponse(utils.ErrServerError))
return
}
idToken, err := verifier.Verify(c, rawIDToken)
if err != nil {
log.Error("OAuth Error: no id_token found")
c.JSON(utils.ErrorStatusCodes[utils.ErrServerError], presenter.CreateErrorResponse(utils.ErrServerError))
return
}

var claims struct {
Name string
Email string `json:"email"`
Verified bool `json:"email_verified"`
}
if err := idToken.Claims(&claims); err != nil {
log.Error("OAuth Error: claims not found")
c.JSON(utils.ErrorStatusCodes[utils.ErrServerError], presenter.CreateErrorResponse(utils.ErrServerError))
return
}
createdAt := time.Now().Unix()

var userData = entities.User{
Name: claims.Name,
Email: claims.Email,
UserName: claims.Email,
Role: entities.RoleUser,
Audit: entities.Audit{
CreatedAt: createdAt,
UpdatedAt: createdAt,
},
}

signedInUser, err := userService.LoginUser(&userData)
if err != nil {
log.Error(err)
c.JSON(utils.ErrorStatusCodes[utils.ErrServerError], presenter.CreateErrorResponse(utils.ErrServerError))
return
}
jwtToken, err := signedInUser.GetSignedJWT()
if err != nil {
log.Error(err)
c.JSON(utils.ErrorStatusCodes[utils.ErrServerError], presenter.CreateErrorResponse(utils.ErrServerError))
return
}
c.Redirect(http.StatusPermanentRedirect, "/login?jwtToken="+jwtToken)
}
}
71 changes: 71 additions & 0 deletions chaoscenter/authentication/api/handlers/rest/misc_handlers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package rest

import (
"litmus/litmus-portal/authentication/pkg/entities"
"litmus/litmus-portal/authentication/pkg/services"

"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
)

func contains(s []string, str string) bool {
for _, v := range s {
if v == str {
return true
}
}

return false
}

type ReadinessAPIStatus struct {
DataBase string `json:"database"`
Collections string `json:"collections"`
}

// Status will request users list and return, if successful,
// an http code 200
func Status(service services.ApplicationService) gin.HandlerFunc {
return func(c *gin.Context) {
_, err := service.GetUsers()
if err != nil {
log.Error(err)
c.JSON(500, entities.APIStatus{"down"})
return
}
c.JSON(200, entities.APIStatus{"up"})
}
}

func Readiness(service services.ApplicationService) gin.HandlerFunc {
return func(c *gin.Context) {
var (
db_flag = "up"
col_flag = "up"
)

dbs, err := service.ListDataBase()
if !contains(dbs, "auth") {
db_flag = "down"
}

if err != nil {
log.Error(err)
c.JSON(500, ReadinessAPIStatus{"down", "unknown"})
return
}

cols, err := service.ListCollection()
if !contains(cols, "project") || !contains(cols, "users") {
col_flag = "down"
}

if err != nil {
log.Error(err)
c.JSON(500, ReadinessAPIStatus{db_flag, "down"})
return
}

c.JSON(200, ReadinessAPIStatus{db_flag, col_flag})
}
}

0 comments on commit e151e20

Please sign in to comment.