Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 153 additions & 0 deletions api/authentication/apikey_handlers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package authentication

import (
"errors"
"net/http"
"time"

"github.com/labstack/echo/v4"
"github.com/latebit-io/bulwarkauth/api/problem"
"github.com/latebit-io/bulwarkauth/internal/authentication"
)

type CreateApiKeyRequest struct {
TenantID string `json:"tenantId"`
AccessToken string `json:"accessToken"`
Name string `json:"name"`
Expires *time.Time `json:"expires"`
}

func (r CreateApiKeyRequest) Validate() error {
if r.TenantID == "" {
return errors.New("tenantId required")
}
if r.AccessToken == "" {
return errors.New("accessToken required")
}
if r.Name == "" {
return errors.New("name required")
}
if r.Expires != nil && r.Expires.Before(time.Now().UTC()) {
return errors.New("expires must be in the future")
}
return nil
}

type ListApiKeyRequest struct {
TenantID string `json:"tenantId"`
AccessToken string `json:"accessToken"`
}

func (r ListApiKeyRequest) Validate() error {
if r.TenantID == "" {
return errors.New("tenantId required")
}
if r.AccessToken == "" {
return errors.New("accessToken required")
}
return nil
}

type DeleteApiKeyRequest struct {
TenantID string `json:"tenantId"`
AccessToken string `json:"accessToken"`
}

func (r DeleteApiKeyRequest) Validate() error {
if r.TenantID == "" {
return errors.New("tenantId required")
}
if r.AccessToken == "" {
return errors.New("accessToken required")
}
return nil
}

type ApiKeyHandlers struct {
apiKeyService authentication.ApiKeyService
}

func NewApiKeyHandlers(service authentication.ApiKeyService) *ApiKeyHandlers {
return &ApiKeyHandlers{apiKeyService: service}
}

func (h *ApiKeyHandlers) Create(c echo.Context) error {
req := new(CreateApiKeyRequest)
err := c.Bind(req)
if err != nil {
httpError := problem.NewBadRequest(err)
return echo.NewHTTPError(httpError.Status, httpError)
}

if err := req.Validate(); err != nil {
httpError := problem.NewBadRequest(err)
return echo.NewHTTPError(httpError.Status, httpError)
}

created, err := h.apiKeyService.Create(c.Request().Context(), req.TenantID, req.AccessToken, req.Name, req.Expires)
if err != nil {
httpError := problem.NewBadRequest(err)
return echo.NewHTTPError(httpError.Status, httpError)
}

return c.JSON(http.StatusCreated, created)
}

func (h *ApiKeyHandlers) List(c echo.Context) error {
req := new(ListApiKeyRequest)
err := c.Bind(req)
if err != nil {
httpError := problem.NewBadRequest(err)
return echo.NewHTTPError(httpError.Status, httpError)
}

if err := req.Validate(); err != nil {
httpError := problem.NewBadRequest(err)
return echo.NewHTTPError(httpError.Status, httpError)
}

keys, err := h.apiKeyService.List(c.Request().Context(), req.TenantID, req.AccessToken)
if err != nil {
httpError := problem.NewBadRequest(err)
return echo.NewHTTPError(httpError.Status, httpError)
}

return c.JSON(http.StatusOK, keys)
}

func (h *ApiKeyHandlers) Delete(c echo.Context) error {
req := new(DeleteApiKeyRequest)
err := c.Bind(req)
if err != nil {
httpError := problem.NewBadRequest(err)
return echo.NewHTTPError(httpError.Status, httpError)
}

if err := req.Validate(); err != nil {
httpError := problem.NewBadRequest(err)
return echo.NewHTTPError(httpError.Status, httpError)
}

prefix := c.Param("prefix")
if prefix == "" {
httpError := problem.NewBadRequest(errors.New("prefix required"))
return echo.NewHTTPError(httpError.Status, httpError)
}

err = h.apiKeyService.Delete(c.Request().Context(), req.TenantID, req.AccessToken, prefix)
if err != nil {
var notFound authentication.ApiKeyNotFoundError
if errors.As(err, &notFound) {
return echo.NewHTTPError(http.StatusNotFound, problem.Details{
Type: "https://latebit.io/bulwark/errors/",
Title: "API Key Not Found",
Status: http.StatusNotFound,
Detail: err.Error(),
})
}
httpError := problem.NewBadRequest(err)
return echo.NewHTTPError(httpError.Status, httpError)
}

return c.NoContent(http.StatusNoContent)
}
9 changes: 9 additions & 0 deletions api/authentication/apikey_routes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package authentication

import "github.com/labstack/echo/v4"

func ApiKeyRoutes(e *echo.Echo, handler *ApiKeyHandlers, middleware ...echo.MiddlewareFunc) {
e.POST("/api/apikeys", handler.Create, middleware...)
e.POST("/api/apikeys/list", handler.List, middleware...)
e.DELETE("/api/apikeys/:prefix", handler.Delete, middleware...)
}
4 changes: 4 additions & 0 deletions cmd/bulwarkauth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ func main() {
socialService.AddValidator(google)
socialHandlers := authenticationapi.NewSocialHandlers(socialService)
authenticationapi.SocialRoutes(service, socialHandlers)
apiKeyRepo := authentication.NewMongoDbApiRepository(mongodb)
apiKeyService := authentication.NewDefaultApiKeyService(apiKeyRepo, encrypt, tokenizer, accountsRepo)
apiKeyHandlers := authenticationapi.NewApiKeyHandlers(apiKeyService)
authenticationapi.ApiKeyRoutes(service, apiKeyHandlers, ratelimiter)

corsSetting(service, config, logger)
apiKeySetting(service, config, logger)
Expand Down
1 change: 1 addition & 0 deletions internal/accounts/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ type Verification struct {
}

type Account struct {
ID string `bson:"id" json:"-"`
TenantID string `bson:"tenantId" json:"tenantId"`
Email string `bson:"email"`
IsVerified bool `bson:"isVerified"`
Expand Down
Loading
Loading