diff --git a/pkg/basic_authentication/README.md b/pkg/basic_authentication/README.md new file mode 100644 index 0000000..9417a88 --- /dev/null +++ b/pkg/basic_authentication/README.md @@ -0,0 +1,54 @@ +# Basic Authentication +Is a reusable package that can be dropped into any draft service as a means of quickly authenticating requests on your api's. It's simple username, and password auth but since it's portable and a simple way to get started. It uses the same primitives as the other authentication methods so if you choose to change your strategy hopefully the upgrade is tenable. + +## Integrations +Router: Each project might have a different http router that is being used so a simple interface that any system can implement has been defined. Additionally, an implementation using the [chi router](https://github.com/go-chi/chi) has been added in `chi.go` with the interface in `router.go`. + +Finally, the storage layer follows the same pattern a reusable interface with a default implementation using [Blueprint](https://github.com/steady-bytes/draft?tab=readme-ov-file#blueprint) + +## How to use +1. Copy, and modify the `html/templates` into your service directory at the root in a template folder `./template` + +2. Initialize the basic_authentication with the repo, and router configuration. Once initialized add to your service router, and configure your middleware (optionally if authenticating your endpoints) a middleware function has already been included. + +```go +// http setup in the service +func NewHTTPHandler(logger chassis.Logger, controller CourseCreatorController, repoUrl string) HTTPHandler { + // setup the blueprint client, this might be optional depending on your repository layer + client := kvv1Connect.NewKeyValueServiceClient(&http.Client{ + Transport: &http2.Transport{ + AllowHTTP: true, + DialTLS: func(network, addr string, _ *tls.Config) (net.Conn, error) { + // If you're also using this client for non-h2c traffic, you may want + // to delegate to tls.Dial if the network isn't TCP or the addr isn't + // in an allowlist. + return net.Dial(network, addr) + }, + }, + }, repoUrl) + + authRepo := ba.NewBlueprintBasicAuthenticationRepository(logger, client) + authController := ba.NewBasicAuthenticationController(authRepo) + authHandler := ba.NewChiBasicAuthenticationHandler(logger, authController) + + return &httpHandler{ + logger: logger, + controller: controller, + authHandler: authHandler, + } +} + +// Implement the chassis.HTTPRegistrar interface for the HTTP handler +func (h *httpHandler) RegisterHTTPHandlers(httpMux *http.ServeMux) { + httpMux.Handle( + "/", + ba.RegisterDefaultAuthRoutes(chi.NewRouter(), h.authHandler), + ) +} +``` + +3. Call middleware in your routes +```go +// don't forget to call the middleware when you need to authenticate a route +authHandler.BasicAuthentication() +``` \ No newline at end of file diff --git a/pkg/basic_authentication/basic_authentication.go b/pkg/basic_authentication/basic_authentication.go new file mode 100644 index 0000000..29109b8 --- /dev/null +++ b/pkg/basic_authentication/basic_authentication.go @@ -0,0 +1,224 @@ +package basic_authentication + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/golang-jwt/jwt/v5" + "github.com/google/uuid" + bav1 "github.com/steady-bytes/draft/api/core/authentication/basic/v1" + "golang.org/x/crypto/bcrypt" + "google.golang.org/protobuf/types/known/timestamppb" +) + +// BasicAuthentication is the service interface for a basic authentication system. +// It defines the methods required for logging in and logging out users. +type BasicAuthentication interface { + Register(ctx context.Context, entity *bav1.Entity) (*bav1.Entity, error) + Login(ctx context.Context, entity *bav1.Entity, remember bool) (*bav1.Session, error) + Logout(ctx context.Context, refreshToken string) error + RefreshAuthToken(ctx context.Context, refreshToken string) (*bav1.Session, error) + ValidateToken(ctx context.Context, token string) error +} + +var ( + ErrUserAlreadyExists = errors.New("user already exists") +) + +func NewBasicAuthenticationController(repo BasicAuthenticationRepository) BasicAuthentication { + return &basicAuthenticationController{ + repository: repo, + } +} + +type basicAuthenticationController struct { + repository BasicAuthenticationRepository +} + +func (c *basicAuthenticationController) Register(ctx context.Context, entity *bav1.Entity) (*bav1.Entity, error) { + found, err := c.repository.Get(ctx, bav1.LookupEntityKeys_LOOKUP_ENTITY_KEY_USERNAME, entity.Username) + if err != nil { + if !errors.Is(err, ErrUserNotFound) { + return nil, fmt.Errorf("failed to check if user exists: %w", err) + } + } + + if found != nil { + return nil, ErrUserAlreadyExists + } + + entity.Password, err = c.hashPassword(entity.Password) + if err != nil { + return nil, fmt.Errorf("failed to hash password: %w", err) + } + + savedEntity, err := c.repository.SaveEntity(ctx, entity) + if err != nil { + return nil, fmt.Errorf("failed to save user: %w", err) + } + + return savedEntity, nil +} + +func (c *basicAuthenticationController) Login(ctx context.Context, entity *bav1.Entity, remember bool) (*bav1.Session, error) { + storedEntity, err := c.repository.Get(ctx, bav1.LookupEntityKeys_LOOKUP_ENTITY_KEY_USERNAME, entity.Username) + if err != nil { + return nil, err + } + + if err := c.checkPassword(storedEntity.Password, entity.Password); err != nil { + return nil, errors.New("invalid credentials") + } + + // Generate JWT token + accessToken, err := c.generateAccessToken(storedEntity.Username) + if err != nil { + return nil, err + } + + session := &bav1.Session{ + Id: uuid.NewString(), + UserId: entity.Id, + CreatedAt: timestamppb.Now(), + Token: accessToken, + // todo make this configurable + ExpiresAt: timestamppb.New(time.Now().Add(24 * time.Hour)), // Default to 24 hours + } + + if remember { + // if remember me is true, create a long-lived session + refreshToken, err := c.generateRefreshToken(storedEntity.Username, session.Id) + if err != nil { + return nil, err + } + session.RefreshToken = refreshToken + + if _, err := c.repository.SaveSession(ctx, session, storedEntity.Username); err != nil { + return nil, err + } + } + + return session, nil +} + +func (c *basicAuthenticationController) Logout(ctx context.Context, refreshToken string) error { + // Parse the JWT token + jwtToken, err := c.parseJWT(refreshToken) + if err != nil { + return err + } + + if claims, ok := jwtToken.Claims.(jwt.MapClaims); ok && jwtToken.Valid { + username := claims["username"].(string) + sessionID := claims["session"].(string) + return c.repository.DeleteSession(ctx, &bav1.Session{Id: sessionID}, username) + } + + return errors.New("invalid refresh token") +} + +func (c *basicAuthenticationController) ValidateToken(ctx context.Context, token string) error { + if _, err := c.parseJWT(token); err != nil { + return err + } + + return nil +} + +func (c *basicAuthenticationController) RefreshAuthToken(ctx context.Context, refreshToken string) (*bav1.Session, error) { + // Parse the JWT token + jwtToken, err := c.parseJWT(refreshToken) + if err != nil { + return nil, err + } + + // Validate the token and extract claims + if claims, ok := jwtToken.Claims.(jwt.MapClaims); ok && jwtToken.Valid { + username := claims["username"].(string) + sessionID := claims["session"].(string) + newAccessToken, err := c.generateAccessToken(username) + if err != nil { + return nil, err + } + return &bav1.Session{ + Id: sessionID, + UserId: username, + Token: newAccessToken, + RefreshToken: refreshToken, + CreatedAt: timestamppb.Now(), + ExpiresAt: timestamppb.New(time.Now().Add(24 * time.Minute)), // Default to 24 minutes + }, nil + } + + return nil, errors.New("invalid refresh token") +} + +//////////// +// UTILITIES +//////////// + +// HashPassword hashes a plain-text password using bcrypt. +func (c *basicAuthenticationController) hashPassword(password string) (string, error) { + hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + return string(hash), err +} + +// CheckPassword compares a bcrypt hashed password with its possible plaintext equivalent. +func (c *basicAuthenticationController) checkPassword(hashedPassword, password string) error { + return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) +} + +// TODO: Read in secret key from environment variable or configuration file +// Replace with your own secret key (keep it safe!) +var jwtSecret = []byte("your-very-secret-key") + +func (c *basicAuthenticationController) generateAccessToken(username string) (string, error) { + // Set custom and standard claims + claims := jwt.MapClaims{ + "username": username, + "exp": time.Now().Add(24 * time.Minute).Unix(), // Expires in 24 minutes + "iat": time.Now().Unix(), // Issued at + } + + // Create the token + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + + // Sign the token with your secret + signedToken, err := token.SignedString(jwtSecret) + if err != nil { + return "", err + } + return signedToken, nil +} + +func (c *basicAuthenticationController) generateRefreshToken(username, sessionID string) (string, error) { + // Set custom and standard claims + claims := jwt.MapClaims{ + "username": username, + "session": sessionID, + "exp": time.Now().Add(24 * time.Hour).Unix(), // Expires in 24 hours + "iat": time.Now().Unix(), // Issued at + } + + // Create the token + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + + // Sign the token with your secret + signedToken, err := token.SignedString(jwtSecret) + if err != nil { + return "", err + } + return signedToken, nil +} + +func (c *basicAuthenticationController) parseJWT(tokenString string) (*jwt.Token, error) { + return jwt.Parse(tokenString, func(token *jwt.Token) (any, error) { + // Validate the signing method + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, jwt.ErrSignatureInvalid + } + return jwtSecret, nil + }) +} diff --git a/pkg/basic_authentication/blueprint.go b/pkg/basic_authentication/blueprint.go new file mode 100644 index 0000000..e828bf9 --- /dev/null +++ b/pkg/basic_authentication/blueprint.go @@ -0,0 +1,124 @@ +package basic_authentication + +import ( + "context" + "errors" + "fmt" + + "connectrpc.com/connect" + bav1 "github.com/steady-bytes/draft/api/core/authentication/basic/v1" + bpc "github.com/steady-bytes/draft/pkg/bpc" + "github.com/steady-bytes/draft/pkg/chassis" + + kvv1 "github.com/steady-bytes/draft/api/core/registry/key_value/v1" + kvv1Connect "github.com/steady-bytes/draft/api/core/registry/key_value/v1/v1connect" +) + +type BlueprintBasicAuthenticationRepository struct { + // Add any fields needed for the repository, such as a database connection + logger chassis.Logger + client kvv1Connect.KeyValueServiceClient +} + +func NewBlueprintBasicAuthenticationRepository(logger chassis.Logger, client kvv1Connect.KeyValueServiceClient) *BlueprintBasicAuthenticationRepository { + return &BlueprintBasicAuthenticationRepository{ + logger: logger, + client: client, + } +} + +func (r *BlueprintBasicAuthenticationRepository) Get(ctx context.Context, key bav1.LookupEntityKeys, val string) (*bav1.Entity, error) { + if key == bav1.LookupEntityKeys_LOOKUP_ENTITY_KEY_UNSPECIFIED { + return nil, ErrInvalidLookupKey + } + + if key == bav1.LookupEntityKeys_LOOKUP_ENTITY_KEY_USERNAME { + // check if the user already exists + entity, err := bpc.GetById[*bav1.Entity]( + ctx, + r.client, + val, + func() *bav1.Entity { + return &bav1.Entity{} + }) + if err != nil { + if cerr, ok := err.(*connect.Error); ok && cerr.Code() == connect.CodeNotFound { + r.logger.Debug("user not found") + return nil, ErrUserNotFound + } + r.logger. + WithError(err). + Error("internal server error") + + return nil, ErrInternalServer + } + return entity, nil + } + + var k string + if key == bav1.LookupEntityKeys_LOOKUP_ENTITY_KEY_EMAIL { + k = "email" + } else if key == bav1.LookupEntityKeys_LOOKUP_ENTITY_KEY_ID { + k = "id" + } else { + return nil, fmt.Errorf("unsupported lookup key: %s", key.String()) + } + + user, err := bpc.ListAndFilter[*bav1.Entity]( + ctx, + r.client, + &kvv1.Statement{ + Where: &kvv1.Statement_KeyVal{ + KeyVal: &kvv1.Equal{ + Match: map[string]string{ + k: val, + }, + }, + }, + }, func() *bav1.Entity { + return &bav1.Entity{} + }) + if err != nil { + return nil, err + } + if len(user) == 0 { + return nil, errors.New("user not found") + } + + return user[0], nil +} + +func (r *BlueprintBasicAuthenticationRepository) SaveEntity(ctx context.Context, entity *bav1.Entity) (*bav1.Entity, error) { + _, err := bpc.Save(ctx, r.client, entity.Username, entity) + if err != nil { + return nil, err + } + + return entity, nil +} + +func (r *BlueprintBasicAuthenticationRepository) SaveSession(ctx context.Context, session *bav1.Session, username string) (*bav1.Session, error) { + key := username + "-session-" + session.GetId() + _, err := bpc.Save(ctx, r.client, key, session) + if err != nil { + return nil, err + } + + return session, nil +} + +func (r *BlueprintBasicAuthenticationRepository) DeleteSession(ctx context.Context, session *bav1.Session, username string) error { + key := username + "-session-" + session.GetId() + + if _, err := r.client.Delete(ctx, connect.NewRequest(&kvv1.DeleteRequest{ + Key: key, + })); err != nil { + r.logger. + WithError(err). + Error("failed to delete session: " + key) + return err + } + + r.logger.Debug("session deleted successfully: " + key) + return nil +} diff --git a/pkg/basic_authentication/chi.go b/pkg/basic_authentication/chi.go new file mode 100644 index 0000000..0126d17 --- /dev/null +++ b/pkg/basic_authentication/chi.go @@ -0,0 +1,319 @@ +package basic_authentication + +import ( + "errors" + "net/http" + "text/template" + "time" + + "github.com/go-chi/chi" + "github.com/steady-bytes/draft/pkg/chassis" + + bav1 "github.com/steady-bytes/draft/api/core/authentication/basic/v1" +) + +// RegisterDefaultAuthRoutes registers login/logout routes on the given router. +func RegisterDefaultAuthRoutes(r *chi.Mux, h BasicAuthenticationHandler) chi.Router { + // Public server side pages for registration and login + r.Get("/register", h.RenderRegistrationPage) // Register the registration handler + r.Get("/login", h.RenderLoginPage) // Register the login handler + + // Form submission handlers + r.Post("/register", h.HandleRegistrationPost) // Handle registration form submission + r.Post("/login", h.HandleLoginPost) // Handle login form submission + + // protected routes for logout and token refresh + r.Group(func(protected chi.Router) { + protected.Use(h.BasicAuthenticationMiddleware) // Middleware to protect routes that require authentication + protected.Post("/logout", h.HandleLogoutPost) // Handle logout requests + protected.Post("/refresh-token", h.RefreshAuthToken) // Handle token refresh requests + }) + + return r +} + +func NewChiBasicAuthenticationHandler(logger chassis.Logger, controller BasicAuthentication) BasicAuthenticationHandler { + return &chiBasicAuthenticationHandler{ + logger: logger, + controller: controller, + } +} + +type chiBasicAuthenticationHandler struct { + logger chassis.Logger + controller BasicAuthentication +} + +type registrationPageData struct { + IsAlreadyRegistered bool +} + +func (h *chiBasicAuthenticationHandler) RenderRegistrationPage(w http.ResponseWriter, r *http.Request) { + h.logger.Debug("registration page with chi router") + + pd := registrationPageData{} + if r.URL.Query().Get("error") == "user_exists" { + pd.IsAlreadyRegistered = true + } + + tmpl, err := template.ParseFiles( + "services/golf-app/app/templates/index.html", + "services/golf-app/app/templates/register.html", + ) + if err != nil { + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + h.logger.Debug("failed to parse registration template: " + err.Error()) + return + } + + // You can pass data to the template if needed, here we pass nil + if err := tmpl.Execute(w, pd); err != nil { + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + h.logger.Debug("failed to execute registration template: " + err.Error()) + return + } +} + +func (h *chiBasicAuthenticationHandler) HandleRegistrationPost(w http.ResponseWriter, r *http.Request) { + h.logger.Debug("handling registration request") + + if err := r.ParseForm(); err != nil { + http.Error(w, "Bad Request", http.StatusBadRequest) + h.logger.Debug("failed to parse form: " + err.Error()) + return + } + + h.logger.Debug("parsed form successfully") + + username := r.FormValue("username") + if username == "" { + http.Error(w, "Username is required", http.StatusBadRequest) + h.logger.Debug("username is required") + return + } + + password := r.FormValue("password") + if password == "" { + http.Error(w, "Password is required", http.StatusBadRequest) + h.logger.Debug("password is required") + return + } + + passwordConfirm := r.FormValue("password-confirmation") + if passwordConfirm == "" { + http.Error(w, "Password confirmation is required", http.StatusBadRequest) + h.logger.Debug("password confirmation is required") + return + } + + if password != passwordConfirm { + http.Error(w, "Passwords do not match", http.StatusBadRequest) + h.logger.Debug("passwords do not match") + return + } + + h.logger.Debug("registration form is valid, proceeding") + + _, err := h.controller.Register(r.Context(), &bav1.Entity{ + Username: username, + Password: password, + }) + if err != nil { + h.logger.Debug("failed to register user: " + err.Error()) + + // TODO: handle specific error cases, e.g., user already exists + if errors.Is(err, ErrUserAlreadyExists) { + h.logger.Debug("user already exists: " + username) + http.Redirect(w, r, "/register?error=user_exists", http.StatusSeeOther) + } else { + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + } + return + } + + http.Redirect(w, r, "/login", http.StatusSeeOther) +} + +func (h *chiBasicAuthenticationHandler) RenderLoginPage(w http.ResponseWriter, r *http.Request) { + h.logger.Debug("login page with chi router") + + // now I need to implement the login page handler + // I think I'm going to user some type of http template to render the login page. + + tmpl, err := template.ParseFiles( + "services/golf-app/app/templates/index.html", + "services/golf-app/app/templates/login.html", + ) + if err != nil { + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + h.logger.Debug("failed to parse login template: " + err.Error()) + return + } + + // You can pass data to the template if needed, here we pass nil + if err := tmpl.Execute(w, nil); err != nil { + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + h.logger.Debug("failed to execute login template: " + err.Error()) + return + } +} + +func (h *chiBasicAuthenticationHandler) HandleLoginPost(w http.ResponseWriter, r *http.Request) { + h.logger.Debug("handling login request") + + if err := r.ParseForm(); err != nil { + http.Error(w, "Bad Request", http.StatusBadRequest) + h.logger.Debug("failed to parse form: " + err.Error()) + return + } + + username := r.FormValue("username") + if username == "" { + http.Error(w, "Username is required", http.StatusBadRequest) + h.logger.Debug("username is required") + return + } + + password := r.FormValue("password") + if password == "" { + http.Error(w, "Password is required", http.StatusBadRequest) + h.logger.Debug("password is required") + return + } + + rememberMe := r.FormValue("remember-me") == "on" + + session, err := h.controller.Login(r.Context(), &bav1.Entity{ + Username: username, + Password: password, + }, rememberMe) + if err != nil { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + h.logger.Debug("failed to login user: " + err.Error()) + return + } + + // redirect the user to the home page of the application after successful with a private cookie + // that contains the session token that can be used to authenticate the user in subsequent requests. + + // Set a secure, HTTP-only cookie with the session token + http.SetCookie(w, &http.Cookie{ + Name: "auth_token", + Value: session.Token, // assuming session.Token is your JWT or session string + Path: "/", + HttpOnly: true, // not accessible via JS + Secure: true, // only sent over HTTPS (set to false for local dev if not using HTTPS) + SameSite: http.SameSiteStrictMode, + Expires: session.ExpiresAt.AsTime(), + }) + + http.SetCookie(w, &http.Cookie{ + Name: "refresh_token", + Value: session.RefreshToken, // assuming session.Token is your JWT or session string + Path: "/", + HttpOnly: true, // not accessible via JS + Secure: true, // only sent over HTTPS (set to false for local dev if not using HTTPS) + SameSite: http.SameSiteStrictMode, + Expires: time.Now().Add(24 * time.Hour), // or time.Now().Add(24 * time.Hour) + }) + + http.Redirect(w, r, "/app", http.StatusSeeOther) +} + +func (h *chiBasicAuthenticationHandler) RefreshAuthToken(w http.ResponseWriter, r *http.Request) { + h.logger.Debug("handling token refresh request") + + // Get the refresh token from the cookie + cookie, err := r.Cookie("refresh_token") + if err != nil || cookie.Value == "" { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + h.logger.Debug("no refresh token cookie found") + return + } + + if err := h.controller.ValidateToken(r.Context(), cookie.Value); err != nil { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + h.logger.Debug("failed to validate refresh token: " + err.Error()) + return + } + + // Generate a new access token + newAccessToken, err := h.controller.RefreshAuthToken(r.Context(), cookie.Value) + if err != nil { + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + h.logger.Debug("failed to generate new access token: " + err.Error()) + return + } + + // Set the new access token in the cookie + http.SetCookie(w, &http.Cookie{ + Name: "auth_token", + Value: newAccessToken.Token, + Path: "/", + HttpOnly: true, + Secure: true, + SameSite: http.SameSiteStrictMode, + Expires: time.Now().Add(24 * time.Minute), + }) + + w.WriteHeader(http.StatusOK) +} + +func (h *chiBasicAuthenticationHandler) HandleLogoutPost(w http.ResponseWriter, r *http.Request) { + h.logger.Debug("handling logout request") + + cookie, err := r.Cookie("refresh_token") + if err != nil || cookie.Value == "" { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + h.logger.Debug("no refresh token cookie found") + return + } + + if err := h.controller.ValidateToken(r.Context(), cookie.Value); err != nil { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + h.logger.Debug("failed to validate refresh token: " + err.Error()) + return + } + + h.controller.Logout(r.Context(), cookie.Value) + + // Clear the session cookie + http.SetCookie(w, &http.Cookie{ + Name: "auth_token", + Value: "", + Path: "/", + HttpOnly: true, + Secure: true, + SameSite: http.SameSiteLaxMode, + Expires: time.Now().Add(-time.Hour), + }) + + http.Redirect(w, r, "/login", http.StatusSeeOther) +} + +// /////////// +// MIDDLEWARE +// /////////// + +// BasicAuthenticationMiddleware is a middleware that checks if the user is authenticated using the basic authentication scheme. +func (h *chiBasicAuthenticationHandler) BasicAuthenticationMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Check if the user is authenticated + cookie, err := r.Cookie("auth_token") + if err != nil || cookie.Value == "" { + http.Redirect(w, r, "/login", http.StatusSeeOther) + return + } + + // check the session token and it's validity + if err := h.controller.ValidateToken(r.Context(), cookie.Value); err != nil { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + h.logger.Debug("failed to parse JWT token: " + err.Error()) + http.Redirect(w, r, "/login", http.StatusSeeOther) + return + } + + // If authenticated, proceed to the next handler + next.ServeHTTP(w, r) + }) +} diff --git a/pkg/basic_authentication/go.mod b/pkg/basic_authentication/go.mod new file mode 100644 index 0000000..fd10e64 --- /dev/null +++ b/pkg/basic_authentication/go.mod @@ -0,0 +1,63 @@ +module github.com/steady-bytes/draft/pkg/basic_authentication + +go 1.24.0 + +require ( + github.com/go-chi/chi v1.5.5 + github.com/steady-bytes/draft/api v1.3.0 + github.com/steady-bytes/draft/pkg/bpc v0.4.0 + github.com/steady-bytes/draft/pkg/chassis v0.6.0 +) + +require ( + github.com/rogpeppe/go-internal v1.13.1 // indirect + github.com/stretchr/testify v1.10.0 // indirect +) + +require ( + connectrpc.com/connect v1.18.1 + connectrpc.com/grpcreflect v1.2.0 // indirect + github.com/armon/go-metrics v0.4.1 // indirect + github.com/boltdb/bolt v1.3.1 // indirect + github.com/cloudevents/sdk-go/binding/format/protobuf/v2 v2.15.0 // indirect + github.com/envoyproxy/go-control-plane v0.12.0 // indirect + github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect + github.com/fatih/color v1.14.1 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/golang-jwt/jwt/v5 v5.3.0 + github.com/google/uuid v1.6.0 + github.com/hashicorp/go-hclog v1.5.0 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-msgpack/v2 v2.1.1 // indirect + github.com/hashicorp/golang-lru v0.5.4 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/hashicorp/raft v1.6.0 // indirect + github.com/hashicorp/raft-boltdb/v2 v2.3.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/rs/cors v1.10.1 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/viper v1.18.2 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + go.etcd.io/bbolt v1.3.7 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.41.0 + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/net v0.42.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/text v0.28.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect + google.golang.org/grpc v1.65.0 // indirect + google.golang.org/protobuf v1.36.7 + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/pkg/basic_authentication/go.sum b/pkg/basic_authentication/go.sum new file mode 100644 index 0000000..e96c097 --- /dev/null +++ b/pkg/basic_authentication/go.sum @@ -0,0 +1,229 @@ +connectrpc.com/connect v1.18.1 h1:PAg7CjSAGvscaf6YZKUefjoih5Z/qYkyaTrBW8xvYPw= +connectrpc.com/connect v1.18.1/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8= +connectrpc.com/grpcreflect v1.2.0 h1:Q6og1S7HinmtbEuBvARLNwYmTbhEGRpHDhqrPNlmK+U= +connectrpc.com/grpcreflect v1.2.0/go.mod h1:nwSOKmE8nU5u/CidgHtPYk1PFI3U9ignz7iDMxOYkSY= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= +github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/cloudevents/sdk-go/binding/format/protobuf/v2 v2.15.0 h1:VIG8z8W5EDy5cIbv2mH5TufcdZm7p/Nn73GZ6nJaXcQ= +github.com/cloudevents/sdk-go/binding/format/protobuf/v2 v2.15.0/go.mod h1:rMOW7HwKadbOR8W4sxYCaHS5TwK0mbpysmDINEfr5SE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.12.0 h1:4X+VP1GHd1Mhj6IB5mMeGbLCleqxjletLK6K0rbxyZI= +github.com/envoyproxy/go-control-plane v0.12.0/go.mod h1:ZBTaoJ23lqITozF0M6G4/IragXCQKCnYbmlmtHvwRG0= +github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= +github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= +github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/go-chi/chi v1.5.5 h1:vOB/HbEMt9QqBqErz07QehcOKHaWFtuj87tTDVz2qXE= +github.com/go-chi/chi v1.5.5/go.mod h1:C9JqLr3tIYjDOZpzn+BCuxY8z8vmca43EeMgyZt7irw= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= +github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= +github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack/v2 v2.1.1 h1:xQEY9yB2wnHitoSzk/B9UjXWRQ67QKu5AOm8aFp8N3I= +github.com/hashicorp/go-msgpack/v2 v2.1.1/go.mod h1:upybraOAblm4S7rx0+jeNy+CWWhzywQsSRV5033mMu4= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/raft v1.6.0 h1:tkIAORZy2GbJ2Trp5eUSggLXDPOJLXC+JJLNMMqtgtM= +github.com/hashicorp/raft v1.6.0/go.mod h1:Xil5pDgeGwRWuX4uPUmwa+7Vagg4N804dz6mhNi6S7o= +github.com/hashicorp/raft-boltdb v0.0.0-20231115180007-027066e4d245 h1:NyeelmxyaUHfDdmhzlEVlrRk+1T9EnlCjvHenrusrfU= +github.com/hashicorp/raft-boltdb v0.0.0-20231115180007-027066e4d245/go.mod h1:nTakvJ4XYq45UXtn0DbwR4aU9ZdjlnIenpbs6Cd+FM0= +github.com/hashicorp/raft-boltdb/v2 v2.3.0 h1:fPpQR1iGEVYjZ2OELvUHX600VAK5qmdnDEv3eXOwZUA= +github.com/hashicorp/raft-boltdb/v2 v2.3.0/go.mod h1:YHukhB04ChJsLHLJEUD6vjFyLX2L3dsX3wPBZcX4tmc= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= +github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= +github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +github.com/steady-bytes/draft/api v1.3.0 h1:0QTwQYOPf++KYT+iuMNZcF+/tf9rj29xAPZwSvgSHrY= +github.com/steady-bytes/draft/api v1.3.0/go.mod h1:zwZNNg8uIoQb3OqibvvcdgJPymawy2rUWh6M6Nk43a0= +github.com/steady-bytes/draft/pkg/bpc v0.4.0 h1:iqlXGyLtCnln8a0KBxjZb/Kixk9USppYXxLJTN08c8w= +github.com/steady-bytes/draft/pkg/bpc v0.4.0/go.mod h1:S3+14c9tmH7nROe2g55HK2vIrsQofZeXFtVVyeL3WMc= +github.com/steady-bytes/draft/pkg/chassis v0.6.0 h1:PuteVjGSFewEIW6VPJXsLemIwnXnG2ncvcYBLOAY+VI= +github.com/steady-bytes/draft/pkg/chassis v0.6.0/go.mod h1:Ee6UcaJ5rJbElW7t0pgryhir3qb12rmHhHKiyrTrdxM= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= +go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= +google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/basic_authentication/repository.go b/pkg/basic_authentication/repository.go new file mode 100644 index 0000000..9efdae1 --- /dev/null +++ b/pkg/basic_authentication/repository.go @@ -0,0 +1,26 @@ +package basic_authentication + +import ( + "context" + "errors" + + bav1 "github.com/steady-bytes/draft/api/core/authentication/basic/v1" +) + +var ( + ErrInvalidLookupKey = errors.New("invalid lookup key") + ErrUserNotFound = errors.New("user not found") + ErrInternalServer = errors.New("internal server error") +) + +// The BasicAuthenticationRepository interface defines the methods that are required for the +// any implementation of a basic authentication repository +type BasicAuthenticationRepository interface { + // get is a simple lookup method that retrieves an entity based on a key-value pair. + // valid keys could be "id", "username", or "email". + Get(ctx context.Context, key bav1.LookupEntityKeys, val string) (*bav1.Entity, error) + + SaveEntity(ctx context.Context, entity *bav1.Entity) (*bav1.Entity, error) + SaveSession(ctx context.Context, session *bav1.Session, username string) (*bav1.Session, error) + DeleteSession(ctx context.Context, session *bav1.Session, username string) error +} diff --git a/pkg/basic_authentication/router.go b/pkg/basic_authentication/router.go new file mode 100644 index 0000000..ca1d439 --- /dev/null +++ b/pkg/basic_authentication/router.go @@ -0,0 +1,17 @@ +package basic_authentication + +import ( + "net/http" +) + +type BasicAuthenticationHandler interface { + RenderRegistrationPage(w http.ResponseWriter, r *http.Request) + HandleRegistrationPost(w http.ResponseWriter, r *http.Request) + RenderLoginPage(w http.ResponseWriter, r *http.Request) + HandleLoginPost(w http.ResponseWriter, r *http.Request) + HandleLogoutPost(w http.ResponseWriter, r *http.Request) + RefreshAuthToken(w http.ResponseWriter, r *http.Request) + + // middlewares for enforcing authentication + BasicAuthenticationMiddleware(next http.Handler) http.Handler +} diff --git a/pkg/basic_authentication/templates/index.html b/pkg/basic_authentication/templates/index.html new file mode 100644 index 0000000..17fdb43 --- /dev/null +++ b/pkg/basic_authentication/templates/index.html @@ -0,0 +1,12 @@ + + +
+