Skip to content

Commit

Permalink
implementing authentication and GetSecret
Browse files Browse the repository at this point in the history
Skarlso committed Jun 17, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent 15feec4 commit 23e272f
Showing 2 changed files with 106 additions and 41 deletions.
68 changes: 53 additions & 15 deletions pkg/bitwarden/bitwarden.go
Original file line number Diff line number Diff line change
@@ -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 {
@@ -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)
}
@@ -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))
})
}
79 changes: 53 additions & 26 deletions pkg/server/server.go
Original file line number Diff line number Diff line change
@@ -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"

@@ -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)
@@ -82,35 +84,60 @@ 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
}

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

return
}

w.WriteHeader(http.StatusOK)
}

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 23e272f

Please sign in to comment.