package httpapi import ( "encoding/base64" "encoding/json" "git.aiterp.net/stufflog3/stufflog3/entities" "git.aiterp.net/stufflog3/stufflog3/models" "git.aiterp.net/stufflog3/stufflog3/usecases/auth" "github.com/gin-gonic/gin" "net/http" "strings" ) type loginInput struct { Username string `json:"username"` Password string `json:"password"` RefreshToken string `json:"refreshToken"` } type setupInput struct { Username string `json:"username"` Password string `json:"password"` Session string `json:"session"` } func Auth(g *gin.RouterGroup, service *auth.Service) { g.GET("", handler("user", func(c *gin.Context) (interface{}, error) { token := c.GetHeader("Authorization") if len(token) < 8 { return nil, models.PermissionDeniedError{} } return service.Provider.ValidateToken(c.Request.Context(), token[7:]), nil })) g.POST("/login", handler("auth", func(c *gin.Context) (interface{}, error) { input := &loginInput{} err := c.BindJSON(input) if err != nil { return nil, models.BadInputError{ Object: "LoginInput", Problem: "Invalid JSON: " + err.Error(), } } return service.Provider.LoginUser(c.Request.Context(), input.Username, input.Password) })) g.POST("/refresh", handler("auth", func(c *gin.Context) (interface{}, error) { input := &loginInput{} err := c.BindJSON(input) if err != nil { return nil, models.BadInputError{ Object: "LoginInput", Problem: "Invalid JSON: " + err.Error(), } } token := c.GetHeader("Authorization") if len(token) < 8 || input.RefreshToken == "" { return nil, models.BadInputError{ Object: "LoginInput", Problem: "Missing token(s)", } } return service.Provider.RefreshUser(c.Request.Context(), token[7:], input.RefreshToken) })) g.POST("/setup", handler("auth", func(c *gin.Context) (interface{}, error) { input := &setupInput{} err := c.BindJSON(input) if err != nil { return nil, models.BadInputError{ Object: "LoginInput", Problem: "Invalid JSON: " + err.Error(), } } return service.Provider.SetupUser(c.Request.Context(), input.Session, input.Username, input.Password) })) } func DummyMiddleware(auth *auth.Service, uuid string) gin.HandlerFunc { return func(c *gin.Context) { c.Request = c.Request.WithContext( auth.ContextWithUser(c.Request.Context(), entities.User{ID: uuid}), ) } } func abortRequest(c *gin.Context) { c.AbortWithStatusJSON(http.StatusUnauthorized, Error{ Code: http.StatusUnauthorized, Message: "You're not supposed to be here!", }) } // TrustingJwtParserMiddleware is meant to be put behind an AWS API gateway that has already // verified this token. func TrustingJwtParserMiddleware(auth *auth.Service) gin.HandlerFunc { return func(c *gin.Context) { authHeader := c.GetHeader("Authorization") split := strings.Split(authHeader, ".") if len(split) >= 3 { data, err := base64.RawStdEncoding.DecodeString(split[1]) if err != nil { abortRequest(c) return } fields := make(map[string]interface{}) err = json.Unmarshal(data, &fields) if err != nil { abortRequest(c) return } if sub, ok := fields["sub"].(string); ok { c.Request = c.Request.WithContext( auth.ContextWithUser(c.Request.Context(), entities.User{ID: sub}), ) } else { abortRequest(c) return } } else { abortRequest(c) } } } // JwtValidatorMiddleware does check the JWT against the provider. func JwtValidatorMiddleware(s *auth.Service) gin.HandlerFunc { return func(c *gin.Context) { header := c.GetHeader("Authorization") if header != "" { user := s.ValidateUser(c.Request.Context(), header[7:]) if user != nil { c.Request = c.Request.WithContext( s.ContextWithUser(c.Request.Context(), *user), ) } } } }