Skip to content

Commit

Permalink
implementing authentication and GetSecret
Browse files Browse the repository at this point in the history
  • Loading branch information
Skarlso committed Jun 17, 2024
1 parent 15feec4 commit f7fccef
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 41 deletions.
68 changes: 53 additions & 15 deletions pkg/bitwarden/bitwarden.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,23 @@ import (

type contextKey string

var contextClientKey contextKey = "warden-client"
var ContextClientKey contextKey = "warden-client"

// Default Settings.
const (
defaultAPIURL = "https://api.bitwarden.com"
defaultIdentityURL = "https://identity.bitwarden.com"
defaultStatePath = ".bitwarden-state"
)

// Defined Header Keys.
const (
WardenHeaderAccessToken = "Warden-Access-Token"
WardenHeaderStatePath = "Warden-State-Path"
WardenHeaderAPIURL = "Warden-Api-Url"
WardenHeaderIdentityURL = "Warden-Identity-Url"
)

// RequestBase contains optional API_URL and IDENTITY_URL values. If not defined,
// defaults are used always.
type RequestBase struct {
Expand All @@ -47,27 +56,31 @@ type LoginRequest struct {
StatePath string `yaml:"statePath,omitempty"`
}

// setOrDefault returns a value if not empty, otherwise a default.
func setOrDefault(v, def string) string {
if v != "" {
return v
}

return def
}

// Login creates a session for further Bitwarden requests.
// Note: I don't like returning the interface, but that's what
// the client returns.
func Login(req *LoginRequest) (sdk.BitwardenClientInterface, error) {
// Configuring the URLS is optional, set them to nil to use the default values
apiURL := defaultAPIURL
identityURL := defaultIdentityURL
apiURL := setOrDefault(req.APIURL, defaultAPIURL)
identityURL := setOrDefault(req.IdentityURL, defaultIdentityURL)
statePath := setOrDefault(req.StatePath, defaultStatePath)

// TODO: Cache the client... or the session?
bitwardenClient, err := sdk.NewBitwardenClient(&apiURL, &identityURL)
if err != nil {
return nil, err
}

defer bitwardenClient.Close()

var statePath string
if req.StatePath == "" {
statePath = defaultStatePath
}

if err := bitwardenClient.AccessTokenLogin(req.AccessToken, &statePath); err != nil {
return nil, fmt.Errorf("bitwarden login: %w", err)
}
Expand All @@ -77,22 +90,47 @@ func Login(req *LoginRequest) (sdk.BitwardenClientInterface, error) {

// Warden is a middleware to use with the bitwarden API.
// Header used by the Warden:
// warden-access-token: <token>
// warden-state-path: <state-path>
// warden-api-url: <url>
// warden-identity-url: <url>
// Warden-Access-Token: <token>
// Warden-State-Path: <state-path>
// Warden-Api-Url: <url>
// Warden-Identity-Url: <url>
// Put the client into the context and so if a context contains our client
// we know that calls are authenticated.
func Warden(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if ctx.Value(contextClientKey) != nil {
if ctx.Value(ContextClientKey) != nil {
next.ServeHTTP(w, r.WithContext(ctx))

return
}

ctx = context.WithValue(ctx, contextClientKey, nil)
defer r.Body.Close()

token := r.Header.Get(WardenHeaderAccessToken)
if token == "" {
http.Error(w, "Missing Warden access token", http.StatusUnauthorized)

return
}

loginRequest := &LoginRequest{
RequestBase: &RequestBase{
APIURL: r.Header.Get(WardenHeaderAPIURL),
IdentityURL: r.Header.Get(WardenHeaderIdentityURL),
},
AccessToken: token,
StatePath: r.Header.Get(WardenHeaderStatePath),
}

client, err := Login(loginRequest)
if err != nil {
http.Error(w, "failed to login to bitwarden using access token: "+err.Error(), http.StatusBadRequest)

return
}

ctx = context.WithValue(ctx, ContextClientKey, client)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
78 changes: 52 additions & 26 deletions pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@ package server

import (
"context"
"encoding/json"
"io"
"log/slog"
"net/http"
"time"

"github.com/bitwarden/sdk-go"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"

Expand Down Expand Up @@ -61,7 +64,6 @@ func (s *Server) Run(_ context.Context) error {
_, _ = w.Write([]byte("live"))
})

//r.Post(api+"/login", s.loginHandler)
// The header will always contain the right credentials.
r.Get(api+"/secret", s.getSecretHandler)
r.Delete(api+"/secret", s.deleteSecretHandler)
Expand All @@ -82,35 +84,59 @@ func (s *Server) Shutdown(ctx context.Context) error {
return s.server.Shutdown(ctx)
}

func (s *Server) getSecretHandler(w http.ResponseWriter, _ *http.Request) {
// GetSecretRequest is the format in which we required secrets to be requested in.
type GetSecretRequest struct {
SecretID string `json:"secretId"`
}

func (s *Server) getSecretHandler(w http.ResponseWriter, r *http.Request) {
content, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)

return
}
defer r.Body.Close()

request := &GetSecretRequest{}
if err := json.Unmarshal(content, request); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)

return
}

client := r.Context().Value(bitwarden.ContextClientKey)
if client == nil {
http.Error(w, "missing client in context, login error", http.StatusInternalServerError)

return
}

c := client.(sdk.BitwardenClientInterface)
secretResponse, err := c.Secrets().Get(request.SecretID)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)

return
}

body, err := json.Marshal(secretResponse)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)

return
}

w.WriteHeader(http.StatusOK)
if _, err := w.Write(body); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)

return
}
}

func (s *Server) deleteSecretHandler(w http.ResponseWriter, _ *http.Request) {
}

func (s *Server) createSecretHandler(w http.ResponseWriter, _ *http.Request) {
}

//func (s *Server) loginHandler(writer http.ResponseWriter, request *http.Request) {
// defer request.Body.Close()
//
// decoder := json.NewDecoder(request.Body)
// loginReq := &bitwarden.LoginRequest{}
//
// if err := decoder.Decode(loginReq); err != nil {
// http.Error(writer, "failed to unmarshal login request: "+err.Error(), http.StatusInternalServerError)
//
// return
// }
//
// client, err := bitwarden.Login(loginReq)
// if err != nil {
// http.Error(writer, "failed to login to bitwarden using access token: "+err.Error(), http.StatusBadRequest)
//
// return
// }
//
// s.Client = client
//
// writer.WriteHeader(http.StatusOK)
//}

0 comments on commit f7fccef

Please sign in to comment.