Skip to content

Commit c667cdb

Browse files
committed
Refactor password authentication to use bcrypt and improve UX
- Replace SHA256 with bcrypt for secure password hashing - Add support for both plain password and pre-hashed password arguments - Remove separate password endpoint, handle POST on login page directly - Improve login form validation with inline error display - Remove session-based error handling for simpler flow - Update UI styling and fix template syntax issues
1 parent af738e6 commit c667cdb

4 files changed

Lines changed: 95 additions & 58 deletions

File tree

main.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ func main() {
2727
var githubClientID string
2828
var githubClientSecret string
2929
var githubAllowedUsers string
30+
var password string
31+
var passwordHash string
3032

3133
rootCmd := &cobra.Command{
3234
Use: "mcp-warp",
@@ -59,6 +61,8 @@ func main() {
5961
githubClientID,
6062
githubClientSecret,
6163
githubAllowedUsersList,
64+
password,
65+
passwordHash,
6266
); err != nil {
6367
panic(err)
6468
}
@@ -70,17 +74,21 @@ func main() {
7074
rootCmd.Flags().StringVarP(&externalURL, "external-url", "e", getEnvWithDefault("EXTERNAL_URL", "http://localhost:8081"), "External URL for the proxy")
7175
rootCmd.Flags().StringVarP(&proxyURL, "proxy-url", "p", getEnvWithDefault("PROXY_URL", "http://localhost:8080"), "Proxy URL for the proxy")
7276
rootCmd.Flags().StringVarP(&globalSecret, "global-secret", "s", getEnvWithDefault("GLOBAL_SECRET", "supersecret"), "Global secret for the proxy")
73-
77+
7478
// Google OAuth configuration
7579
rootCmd.Flags().StringVar(&googleClientID, "google-client-id", getEnvWithDefault("GOOGLE_CLIENT_ID", ""), "Google OAuth client ID")
7680
rootCmd.Flags().StringVar(&googleClientSecret, "google-client-secret", getEnvWithDefault("GOOGLE_CLIENT_SECRET", ""), "Google OAuth client secret")
7781
rootCmd.Flags().StringVar(&googleAllowedUsers, "google-allowed-users", getEnvWithDefault("GOOGLE_ALLOWED_USERS", ""), "Comma-separated list of allowed Google users (emails)")
78-
82+
7983
// GitHub OAuth configuration
8084
rootCmd.Flags().StringVar(&githubClientID, "github-client-id", getEnvWithDefault("GITHUB_CLIENT_ID", ""), "GitHub OAuth client ID")
8185
rootCmd.Flags().StringVar(&githubClientSecret, "github-client-secret", getEnvWithDefault("GITHUB_CLIENT_SECRET", ""), "GitHub OAuth client secret")
8286
rootCmd.Flags().StringVar(&githubAllowedUsers, "github-allowed-users", getEnvWithDefault("GITHUB_ALLOWED_USERS", ""), "Comma-separated list of allowed GitHub users (usernames)")
8387

88+
// Password authentication
89+
rootCmd.Flags().StringVar(&password, "password", getEnvWithDefault("PASSWORD", ""), "Plain text password for authentication (will be hashed with bcrypt)")
90+
rootCmd.Flags().StringVar(&passwordHash, "password-hash", getEnvWithDefault("PASSWORD_HASH", ""), "Bcrypt hash of password for authentication")
91+
8492
if err := rootCmd.Execute(); err != nil {
8593
panic(err)
8694
}

pkg/auth/main.go

Lines changed: 47 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,13 @@ package auth
22

33
import (
44
"context"
5-
"crypto/sha256"
65
"embed"
7-
"fmt"
86
"html/template"
97
"net/http"
10-
"strings"
118

129
"github.com/gin-contrib/sessions"
1310
"github.com/gin-gonic/gin"
11+
"golang.org/x/crypto/bcrypt"
1412
"golang.org/x/oauth2"
1513
)
1614

@@ -61,7 +59,6 @@ func NewAuthRouter(passwordHash []string, providers ...Provider) (*AuthRouter, e
6159
const (
6260
LoginEndpoint = "/.auth/login"
6361
LogoutEndpoint = "/.auth/logout"
64-
PasswordEndpoint = "/.auth/password"
6562
GoogleAuthEndpoint = "/.auth/google"
6663
GoogleCallbackEndpoint = "/.auth/google/callback"
6764
GitHubAuthEndpoint = "/.auth/github"
@@ -70,7 +67,7 @@ const (
7067

7168
func (a *AuthRouter) SetupRoutes(router gin.IRouter) {
7269
router.GET(LoginEndpoint, a.handleLogin)
73-
router.POST(PasswordEndpoint, a.handlePasswordAuth)
70+
router.POST(LoginEndpoint, a.handleLoginPost)
7471
router.GET(LogoutEndpoint, a.handleLogout)
7572
for providerName, provider := range a.providers {
7673
router.GET(provider.RedirectURL(), func(c *gin.Context) {
@@ -110,6 +107,11 @@ type ProviderData struct {
110107
}
111108

112109
func (a *AuthRouter) handleLogin(c *gin.Context) {
110+
if c.Request.Method == "POST" {
111+
a.handleLoginPost(c)
112+
return
113+
}
114+
113115
var providersData []ProviderData
114116
for name := range a.providers {
115117
providersData = append(providersData, ProviderData{
@@ -118,24 +120,14 @@ func (a *AuthRouter) handleLogin(c *gin.Context) {
118120
})
119121
}
120122

121-
session := sessions.Default(c)
122-
passwordError := session.Get("password_error")
123-
session.Delete("password_error")
124-
session.Save()
125-
126-
var passwordErrorStr string
127-
if passwordError != nil {
128-
passwordErrorStr = passwordError.(string)
129-
}
130-
131123
data := struct {
132124
Providers []ProviderData
133125
HasPassword bool
134126
PasswordError string
135127
}{
136128
Providers: providersData,
137129
HasPassword: len(a.passswordHash) > 0,
138-
PasswordError: passwordErrorStr,
130+
PasswordError: "",
139131
}
140132

141133
c.Header("Content-Type", "text/html; charset=utf-8")
@@ -145,31 +137,52 @@ func (a *AuthRouter) handleLogin(c *gin.Context) {
145137
}
146138
}
147139

148-
func (a *AuthRouter) handlePasswordAuth(c *gin.Context) {
140+
func (a *AuthRouter) handleLoginPost(c *gin.Context) {
149141
password := c.PostForm("password")
142+
var errorMessage string
143+
150144
if password == "" {
151-
session := sessions.Default(c)
152-
session.Set("password_error", "Password is required")
153-
session.Save()
154-
c.Redirect(http.StatusFound, LoginEndpoint)
155-
return
145+
errorMessage = "Password is required"
146+
} else {
147+
var isValid bool
148+
for _, hash := range a.passswordHash {
149+
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
150+
if err == nil {
151+
isValid = true
152+
break
153+
}
154+
}
155+
156+
if !isValid {
157+
errorMessage = "Invalid password"
158+
}
156159
}
157160

158-
hashedPassword := fmt.Sprintf("%x", sha256.Sum256([]byte(password)))
161+
if errorMessage != "" {
162+
var providersData []ProviderData
163+
for name := range a.providers {
164+
providersData = append(providersData, ProviderData{
165+
Name: name,
166+
URL: a.providers[name].AuthURL(),
167+
})
168+
}
159169

160-
var isValid bool
161-
for _, hash := range a.passswordHash {
162-
if strings.EqualFold(hashedPassword, hash) {
163-
isValid = true
164-
break
170+
data := struct {
171+
Providers []ProviderData
172+
HasPassword bool
173+
PasswordError string
174+
}{
175+
Providers: providersData,
176+
HasPassword: len(a.passswordHash) > 0,
177+
PasswordError: errorMessage,
165178
}
166-
}
167179

168-
if !isValid {
169-
session := sessions.Default(c)
170-
session.Set("password_error", "Invalid password")
171-
session.Save()
172-
c.Redirect(http.StatusFound, LoginEndpoint)
180+
c.Header("Content-Type", "text/html; charset=utf-8")
181+
c.Status(http.StatusBadRequest)
182+
if err := a.template.Execute(c.Writer, data); err != nil {
183+
c.AbortWithError(http.StatusInternalServerError, err)
184+
return
185+
}
173186
return
174187
}
175188

pkg/auth/templates/login.html

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<style>
88
body {
99
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
10-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
10+
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
1111
margin: 0;
1212
padding: 0;
1313
min-height: 100vh;
@@ -31,11 +31,6 @@
3131
font-size: 2rem;
3232
font-weight: 600;
3333
}
34-
.password-form {
35-
margin-bottom: 2rem;
36-
padding-bottom: 2rem;
37-
border-bottom: 1px solid #e5e5e5;
38-
}
3934
.form-group {
4035
margin-bottom: 1.5rem;
4136
text-align: left;
@@ -85,7 +80,7 @@
8580
font-size: 0.9rem;
8681
}
8782
.divider::before {
88-
content: '';
83+
content: "";
8984
position: absolute;
9085
top: 50%;
9186
left: 0;
@@ -122,18 +117,18 @@
122117
transform: translateY(-2px);
123118
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15);
124119
}
125-
.google {
120+
.Google {
126121
background-color: #4285f4;
127122
color: white;
128123
}
129-
.google:hover {
124+
.Google:hover {
130125
background-color: #357ae8;
131126
}
132-
.github {
127+
.GitHub {
133128
background-color: #333;
134129
color: white;
135130
}
136-
.github:hover {
131+
.GitHub:hover {
137132
background-color: #24292e;
138133
}
139134
.error-message {
@@ -147,28 +142,30 @@
147142
<body>
148143
<div class="login-container">
149144
<h1>MCP Auth Proxy</h1>
150-
145+
151146
{{if .HasPassword}}
152-
<form class="password-form" action="/.auth/password" method="POST">
147+
<form class="password-form" action="/.auth/login" method="POST">
153148
<div class="form-group">
154149
<label class="form-label" for="password">Password</label>
155-
<input class="form-input" type="password" id="password" name="password" required>
150+
<input
151+
class="form-input"
152+
type="password"
153+
id="password"
154+
name="password"
155+
required
156+
/>
156157
</div>
157158
<button class="password-button" type="submit">Login</button>
158159
{{if .PasswordError}}
159160
<div class="error-message">{{.PasswordError}}</div>
160161
{{end}}
161162
</form>
162-
{{end}}
163-
164-
{{if and .HasPassword .Providers}}
163+
{{end}} {{if and .HasPassword (gt (len .Providers) 0)}}
165164
<div class="divider">
166165
<span>or</span>
167166
</div>
168-
{{end}}
169-
170-
{{range .Providers}}
171-
<a href="{{.URL}}" class="provider-button {{.Name | lower}}">
167+
{{end}} {{range .Providers}}
168+
<a href="{{.URL}}" class="provider-button {{.Name }}">
172169
Login with {{.Name}}
173170
</a>
174171
{{end}}

pkg/mcp-proxy/main.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"github.com/sigbit/mcp-auth-proxy/pkg/repository"
2020
"github.com/sigbit/mcp-auth-proxy/pkg/utils"
2121
"go.uber.org/zap"
22+
"golang.org/x/crypto/bcrypt"
2223
)
2324

2425
func Run(
@@ -33,6 +34,8 @@ func Run(
3334
githubClientID string,
3435
githubClientSecret string,
3536
githubAllowedUsers []string,
37+
password string,
38+
passwordHash string,
3639
) error {
3740
parsedExternalURL, err := url.Parse(externalURL)
3841
if err != nil {
@@ -89,7 +92,23 @@ func Run(
8992
providers = append(providers, githubProvider)
9093
}
9194

92-
authRouter, err := auth.NewAuthRouter([]string{}, providers...)
95+
var passwordHashes []string
96+
97+
// Handle password argument - generate bcrypt hash if provided
98+
if password != "" {
99+
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
100+
if err != nil {
101+
return fmt.Errorf("failed to generate password hash: %w", err)
102+
}
103+
passwordHashes = append(passwordHashes, string(hash))
104+
}
105+
106+
// Handle password-hash argument - use directly if provided
107+
if passwordHash != "" {
108+
passwordHashes = append(passwordHashes, passwordHash)
109+
}
110+
111+
authRouter, err := auth.NewAuthRouter(passwordHashes, providers...)
93112
if err != nil {
94113
return fmt.Errorf("failed to create auth router: %w", err)
95114
}

0 commit comments

Comments
 (0)