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 }