feat: includeUser URL parameter for login handler
This commit is contained in:
parent
b393f1a47c
commit
15c4666ace
server/pkg/service
@ -5,6 +5,8 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"git.umbrella.haus/ae/notatest/pkg/data"
|
"git.umbrella.haus/ae/notatest/pkg/data"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
@ -16,6 +18,15 @@ import (
|
|||||||
|
|
||||||
type userCtxKey struct{}
|
type userCtxKey struct{}
|
||||||
|
|
||||||
|
// Stripped object that only contains non-critical data
|
||||||
|
type userResponse struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
CreatedAt *time.Time `json:"created_at"`
|
||||||
|
UpdatedAt *time.Time `json:"updated_at"`
|
||||||
|
IsAdmin bool `json:"is_admin"`
|
||||||
|
}
|
||||||
|
|
||||||
// Mockable database operations interface
|
// Mockable database operations interface
|
||||||
type UserStore interface {
|
type UserStore interface {
|
||||||
CreateUser(ctx context.Context, arg data.CreateUserParams) (data.User, error)
|
CreateUser(ctx context.Context, arg data.CreateUserParams) (data.User, error)
|
||||||
@ -160,10 +171,23 @@ func (rs usersResource) Login(w http.ResponseWriter, r *http.Request) {
|
|||||||
SameSite: http.SameSiteStrictMode,
|
SameSite: http.SameSiteStrictMode,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Return the access token in the response body (it should be stored in browser's memory client-side)
|
// Build response
|
||||||
respondJSON(w, http.StatusOK, map[string]string{
|
response := map[string]any{
|
||||||
"access_token": tokenPair.AccessToken,
|
"access_token": tokenPair.AccessToken,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
// Include user data if the client has requested it (`?includeUser=true`)
|
||||||
|
if includeUser, _ := strconv.ParseBool(r.URL.Query().Get("includeUser")); includeUser {
|
||||||
|
response["user"] = userResponse{
|
||||||
|
ID: user.ID,
|
||||||
|
Username: user.Username,
|
||||||
|
CreatedAt: user.CreatedAt,
|
||||||
|
UpdatedAt: user.UpdatedAt,
|
||||||
|
IsAdmin: user.IsAdmin,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
respondJSON(w, http.StatusOK, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs usersResource) List(w http.ResponseWriter, r *http.Request) {
|
func (rs usersResource) List(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -295,7 +296,9 @@ func TestUsersLogin(t *testing.T) {
|
|||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
requestBody interface{}
|
includeUser string
|
||||||
|
wantUserData bool
|
||||||
|
requestBody any
|
||||||
mockSetup func(*mockUserStore)
|
mockSetup func(*mockUserStore)
|
||||||
wantStatus int
|
wantStatus int
|
||||||
wantResponse string
|
wantResponse string
|
||||||
@ -337,7 +340,25 @@ func TestUsersLogin(t *testing.T) {
|
|||||||
wantResponse: `{"error":"Invalid credentials"}`,
|
wantResponse: `{"error":"Invalid credentials"}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "successful login",
|
name: "successful login with user data",
|
||||||
|
includeUser: "true",
|
||||||
|
wantUserData: true,
|
||||||
|
requestBody: map[string]string{
|
||||||
|
"username": testUser.Username,
|
||||||
|
"password": validPassword,
|
||||||
|
},
|
||||||
|
mockSetup: func(m *mockUserStore) {
|
||||||
|
m.GetUserByUsernameFunc = func(_ context.Context, username string) (data.User, error) {
|
||||||
|
return testUser, nil
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantStatus: http.StatusOK,
|
||||||
|
checkCookie: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "successful login without user data",
|
||||||
|
includeUser: "false",
|
||||||
|
wantUserData: false,
|
||||||
requestBody: map[string]string{
|
requestBody: map[string]string{
|
||||||
"username": testUser.Username,
|
"username": testUser.Username,
|
||||||
"password": validPassword,
|
"password": validPassword,
|
||||||
@ -366,6 +387,11 @@ func TestUsersLogin(t *testing.T) {
|
|||||||
req := httptest.NewRequest("POST", "/login", bytes.NewReader(body))
|
req := httptest.NewRequest("POST", "/login", bytes.NewReader(body))
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
// Add the necessary query parameters
|
||||||
|
q := url.Values{}
|
||||||
|
q.Add("includeUser", tt.includeUser)
|
||||||
|
req.URL.RawQuery = q.Encode()
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
rs.Login(w, req)
|
rs.Login(w, req)
|
||||||
|
|
||||||
@ -377,6 +403,18 @@ func TestUsersLogin(t *testing.T) {
|
|||||||
t.Errorf("expected response %q, got %q", tt.wantResponse, w.Body.String())
|
t.Errorf("expected response %q, got %q", tt.wantResponse, w.Body.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tt.wantUserData {
|
||||||
|
var response struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
User data.User `json:"user"` // Cast to the "raw" type to allow checking for sensitive data fields
|
||||||
|
}
|
||||||
|
json.Unmarshal(w.Body.Bytes(), &response)
|
||||||
|
|
||||||
|
assert.Equal(t, testUser.ID, response.User.ID)
|
||||||
|
assert.Equal(t, testUser.Username, response.User.Username)
|
||||||
|
assert.Empty(t, response.User.PasswordHash) // Ensure sensitive data excluded
|
||||||
|
}
|
||||||
|
|
||||||
if tt.checkCookie {
|
if tt.checkCookie {
|
||||||
cookies := w.Result().Cookies()
|
cookies := w.Result().Cookies()
|
||||||
var refreshCookie *http.Cookie
|
var refreshCookie *http.Cookie
|
||||||
@ -407,7 +445,7 @@ func TestUsersLogin(t *testing.T) {
|
|||||||
token, err := jwt.ParseWithClaims(
|
token, err := jwt.ParseWithClaims(
|
||||||
response["access_token"],
|
response["access_token"],
|
||||||
&userClaims{},
|
&userClaims{},
|
||||||
func(token *jwt.Token) (interface{}, error) {
|
func(token *jwt.Token) (any, error) {
|
||||||
return []byte(jwtSecret), nil
|
return []byte(jwtSecret), nil
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user