github.com/nais/knorten@v0.0.0-20240104110906-55926958e361/pkg/api/api.go (about)

     1  package api
     2  
     3  import (
     4  	"database/sql"
     5  	"errors"
     6  	"fmt"
     7  	"net/http"
     8  	"text/template"
     9  
    10  	"github.com/gin-gonic/gin"
    11  	"github.com/nais/knorten/pkg/api/auth"
    12  	"github.com/nais/knorten/pkg/database"
    13  	"github.com/sirupsen/logrus"
    14  )
    15  
    16  type client struct {
    17  	azureClient  *auth.Azure
    18  	router       *gin.Engine
    19  	repo         *database.Repo
    20  	log          *logrus.Entry
    21  	dryRun       bool
    22  	adminGroupID string
    23  	gcpProject   string
    24  	gcpZone      string
    25  }
    26  
    27  func New(repo *database.Repo, azureClient *auth.Azure, dryRun bool, sessionKey, adminGroupEmail, gcpProject, gcpZone string, log *logrus.Entry) (*gin.Engine, error) {
    28  	router := gin.New()
    29  
    30  	router.Use(gin.Recovery())
    31  	router.Use(func(ctx *gin.Context) {
    32  		log.WithField("subsystem", "gin").Infof("%v %v %v", ctx.Request.Method, ctx.Request.URL.Path, ctx.Writer.Status())
    33  	})
    34  
    35  	api := client{
    36  		azureClient: azureClient,
    37  		router:      router,
    38  		repo:        repo,
    39  		log:         log,
    40  		dryRun:      dryRun,
    41  		gcpProject:  gcpProject,
    42  		gcpZone:     gcpZone,
    43  	}
    44  
    45  	session, err := repo.NewSessionStore(sessionKey)
    46  	if err != nil {
    47  		return nil, err
    48  	}
    49  
    50  	api.router.Use(session)
    51  	api.router.Static("/assets", "./assets")
    52  	api.router.FuncMap = template.FuncMap{
    53  		"toArray": toArray,
    54  	}
    55  	api.router.LoadHTMLGlob("templates/**/*")
    56  	api.setupUnauthenticatedRoutes()
    57  	api.router.Use(api.authMiddleware())
    58  	api.setupAuthenticatedRoutes()
    59  	api.router.Use(api.adminAuthMiddleware())
    60  	api.setupAdminRoutes()
    61  	err = api.fetchAdminGroupID(adminGroupEmail)
    62  	if err != nil {
    63  		return nil, err
    64  	}
    65  
    66  	return router, nil
    67  }
    68  
    69  func Run(router *gin.Engine, inCluster bool) error {
    70  	if inCluster {
    71  		return router.Run()
    72  	}
    73  
    74  	return router.Run("localhost:8080")
    75  }
    76  
    77  func (c *client) setupUnauthenticatedRoutes() {
    78  	c.router.GET("/", func(ctx *gin.Context) {
    79  		c.htmlResponseWrapper(ctx, http.StatusOK, "index", gin.H{})
    80  	})
    81  
    82  	c.setupAuthRoutes()
    83  }
    84  
    85  func (c *client) setupAuthenticatedRoutes() {
    86  	c.setupUserRoutes()
    87  	c.setupTeamRoutes()
    88  	c.setupComputeRoutes()
    89  	c.setupSecretRoutes()
    90  	c.setupChartRoutes()
    91  }
    92  
    93  func (c *client) htmlResponseWrapper(ctx *gin.Context, status int, tmplName string, values gin.H) {
    94  	values["loggedIn"] = c.isLoggedIn(ctx)
    95  	values["isAdmin"] = c.isAdmin(ctx)
    96  
    97  	ctx.HTML(status, tmplName, values)
    98  }
    99  
   100  func (c *client) isLoggedIn(ctx *gin.Context) bool {
   101  	cookie, err := ctx.Cookie(sessionCookie)
   102  	if err != nil {
   103  		if errors.Is(err, http.ErrNoCookie) {
   104  			return false
   105  		}
   106  		c.log.WithError(err).Error("reading session cookie")
   107  		return false
   108  	}
   109  
   110  	session, err := c.repo.SessionGet(ctx, cookie)
   111  	if err != nil {
   112  		if errors.Is(err, sql.ErrNoRows) {
   113  			return false
   114  		}
   115  		c.log.WithError(err).Error("retrieving session from db")
   116  		return false
   117  	}
   118  
   119  	return session.Token != ""
   120  }
   121  
   122  func (c *client) isAdmin(ctx *gin.Context) bool {
   123  	cookie, err := ctx.Cookie(sessionCookie)
   124  	if err != nil {
   125  		if errors.Is(err, http.ErrNoCookie) {
   126  			return false
   127  		}
   128  		c.log.WithError(err).Error("reading session cookie")
   129  		return false
   130  	}
   131  
   132  	session, err := c.repo.SessionGet(ctx, cookie)
   133  	if err != nil {
   134  		if errors.Is(err, sql.ErrNoRows) {
   135  			return false
   136  		}
   137  		c.log.WithError(err).Error("retrieving session from db")
   138  		return false
   139  	}
   140  
   141  	return session.IsAdmin
   142  }
   143  
   144  func (c *client) fetchAdminGroupID(adminGroupEmail string) error {
   145  	id, err := c.azureClient.GetGroupID(adminGroupEmail)
   146  	if err != nil {
   147  		return fmt.Errorf("retrieve admin group id error: %v", err)
   148  	}
   149  
   150  	c.adminGroupID = id
   151  	return nil
   152  }
   153  
   154  func toArray(args ...any) []any {
   155  	return args
   156  }