Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,37 @@ start-temporal:
start-temporal-server:
cd $(SERVER_DIR) && $(BACKEND_ENV_VARS) go run ./cmd/temporal-worker/main.go

COOKIE_JAR := /tmp/olake_cookies.txt

# Create a user with specified username, password and email (e.g. make create-user username=admin password=admin123 email=admin@example.com)
create-user:
@curl -s -X POST http://localhost:8000/signup -H "Content-Type: application/json" -d "{\"username\":\"$(username)\",\"password\":\"$(password)\",\"email\":\"$(email)\"}" | grep -q "\"success\": true" && echo "User $(username) created successfully" || echo "Failed to create user $(username)"
@curl -s -X POST http://localhost:8000/signup \
-H "Content-Type: application/json" \
-d "{\"username\":\"$(username)\",\"password\":\"$(password)\",\"email\":\"$(email)\"}" | grep -q "\"success\": true" && echo "User $(username) created successfully" || echo "Failed to create user $(username)"


# helper target that logs in with provided credentials and stores cookie
# prints an error and exits if login fails or no cookie received
login:
@echo "logging in as '$(oldusername)'..."
@rm -f $(COOKIE_JAR)
@curl -c $(COOKIE_JAR) -s -X POST http://localhost:8000/login \
-H 'Content-Type: application/json' \
-d '{"username":"$(oldusername)","password":"$(oldpassword)"}' \
| tee /dev/stderr | grep -q "\"success\": true" \
&& [ -s $(COOKIE_JAR) ] \
&& echo "login succeeded" \
|| (echo "login failed or no session cookie written"; exit 1)

# Update an existing user's credentials.
# Pass oldusername, oldpassword, newusername and newpassword variables.
# Example: make update-user oldusername=admin oldpassword=secret newusername=alice newpassword=newpass
update-user: login
@echo "updating credentials to $(newusername)..."
@curl -b $(COOKIE_JAR) -s -X PUT http://localhost:8000/user/credentials \
-H "Content-Type: application/json" \
-d "{\"username\":\"$(newusername)\",\"password\":\"$(newpassword)\"}" \
| tee /dev/stderr | grep -q "\"success\": true" \
&& echo "Credentials updated successfully" \
|| echo "Failed to update credentials"

85 changes: 85 additions & 0 deletions server/docs/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -2669,9 +2669,64 @@ const docTemplate = `{
}
}
}
},
"/user/credentials": {
"put": {
"description": "Change the authenticated user's credentials. User must be logged in via session.",
"tags": [
"Authentication"
],
"summary": "Update user credentials",
"parameters": [
{
"description": "new username/password",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.UpdateCredentialsRequest"
}
}
],
"responses": {
"200": {
"description": "credentials updated successfully",
"schema": {
"$ref": "#/definitions/dto.JSONResponse"
}
},
"400": {
"description": "invalid request",
"schema": {
"$ref": "#/definitions/dto.Error400Response"
}
},
"401": {
"description": "not authenticated",
"schema": {
"$ref": "#/definitions/dto.Error401Response"
}
},
"500": {
"description": "failed to update credentials",
"schema": {
"$ref": "#/definitions/dto.Error500Response"
}
}
}
}
}
},
"definitions": {
"dto.AdvancedSettings": {
"type": "object",
"properties": {
"max_discover_threads": {
"type": "integer",
"example": 50
}
}
},
"dto.CheckUniqueJobNameResponse": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -2753,6 +2808,9 @@ const docTemplate = `{
"type": "boolean",
"example": true
},
"advanced_settings": {
"$ref": "#/definitions/dto.AdvancedSettings"
},
"destination": {
"$ref": "#/definitions/dto.DriverConfig"
},
Expand Down Expand Up @@ -3046,6 +3104,9 @@ const docTemplate = `{
"type": "boolean",
"example": true
},
"advanced_settings": {
"$ref": "#/definitions/dto.AdvancedSettings"
},
"created_at": {
"type": "string",
"example": "2024-01-01T00:00:00Z"
Expand Down Expand Up @@ -3390,6 +3451,10 @@ const docTemplate = `{
"type": "string",
"example": "my-sync-job"
},
"max_discover_threads": {
"type": "integer",
"example": 50
},
"name": {
"type": "string",
"example": "my-postgres-source"
Expand Down Expand Up @@ -3458,6 +3523,23 @@ const docTemplate = `{
}
}
},
"dto.UpdateCredentialsRequest": {
"type": "object",
"required": [
"password",
"username"
],
"properties": {
"password": {
"type": "string",
"example": "newpassword"
},
"username": {
"type": "string",
"example": "newadmin"
}
}
},
"dto.UpdateDestinationRequest": {
"type": "object",
"required": [
Expand Down Expand Up @@ -3499,6 +3581,9 @@ const docTemplate = `{
"type": "boolean",
"example": true
},
"advanced_settings": {
"$ref": "#/definitions/dto.AdvancedSettings"
},
"destination": {
"$ref": "#/definitions/dto.DriverConfig"
},
Expand Down
85 changes: 85 additions & 0 deletions server/docs/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -2660,9 +2660,64 @@
}
}
}
},
"/user/credentials": {
"put": {
"description": "Change the authenticated user's credentials. User must be logged in via session.",
"tags": [
"Authentication"
],
"summary": "Update user credentials",
"parameters": [
{
"description": "new username/password",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.UpdateCredentialsRequest"
}
}
],
"responses": {
"200": {
"description": "credentials updated successfully",
"schema": {
"$ref": "#/definitions/dto.JSONResponse"
}
},
"400": {
"description": "invalid request",
"schema": {
"$ref": "#/definitions/dto.Error400Response"
}
},
"401": {
"description": "not authenticated",
"schema": {
"$ref": "#/definitions/dto.Error401Response"
}
},
"500": {
"description": "failed to update credentials",
"schema": {
"$ref": "#/definitions/dto.Error500Response"
}
}
}
}
}
},
"definitions": {
"dto.AdvancedSettings": {
"type": "object",
"properties": {
"max_discover_threads": {
"type": "integer",
"example": 50
}
}
},
"dto.CheckUniqueJobNameResponse": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -2744,6 +2799,9 @@
"type": "boolean",
"example": true
},
"advanced_settings": {
"$ref": "#/definitions/dto.AdvancedSettings"
},
"destination": {
"$ref": "#/definitions/dto.DriverConfig"
},
Expand Down Expand Up @@ -3037,6 +3095,9 @@
"type": "boolean",
"example": true
},
"advanced_settings": {
"$ref": "#/definitions/dto.AdvancedSettings"
},
"created_at": {
"type": "string",
"example": "2024-01-01T00:00:00Z"
Expand Down Expand Up @@ -3381,6 +3442,10 @@
"type": "string",
"example": "my-sync-job"
},
"max_discover_threads": {
"type": "integer",
"example": 50
},
"name": {
"type": "string",
"example": "my-postgres-source"
Expand Down Expand Up @@ -3449,6 +3514,23 @@
}
}
},
"dto.UpdateCredentialsRequest": {
"type": "object",
"required": [
"password",
"username"
],
"properties": {
"password": {
"type": "string",
"example": "newpassword"
},
"username": {
"type": "string",
"example": "newadmin"
}
}
},
"dto.UpdateDestinationRequest": {
"type": "object",
"required": [
Expand Down Expand Up @@ -3490,6 +3572,9 @@
"type": "boolean",
"example": true
},
"advanced_settings": {
"$ref": "#/definitions/dto.AdvancedSettings"
},
"destination": {
"$ref": "#/definitions/dto.DriverConfig"
},
Expand Down
41 changes: 41 additions & 0 deletions server/internal/handlers/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,47 @@ func (h *Handler) Signup() {
})
}

// @Summary Update user credentials
// @Tags Authentication
// @Description Change the authenticated user's credentials. User must be logged in via session.
// @Param body body dto.UpdateCredentialsRequest true "new username/password"
// @Success 200 {object} dto.JSONResponse "credentials updated successfully"
// @Failure 400 {object} dto.Error400Response "invalid request"
// @Failure 401 {object} dto.Error401Response "not authenticated"
// @Failure 500 {object} dto.Error500Response "failed to update credentials"
// @Router /user/credentials [put]
func (h *Handler) UpdateCredentials() {
// ensure user is authenticated via session
userID := h.GetSession(constants.SessionUserID)
if userID == nil {
utils.ErrorResponse(&h.Controller, http.StatusUnauthorized, "Not authenticated", errors.New("not authenticated"))
return
}
userIDInt, ok := userID.(int)
if !ok {
utils.ErrorResponse(&h.Controller, http.StatusUnauthorized, "Invalid session data", errors.New("invalid session user id"))
return
}

var req dto.UpdateCredentialsRequest
if err := UnmarshalAndValidate(h.Ctx.Input.RequestBody, &req); err != nil {
utils.ErrorResponse(&h.Controller, http.StatusBadRequest, constants.ValidationInvalidRequestFormat, err)
return
}

if req.Username == "" && req.Password == "" {
utils.ErrorResponse(&h.Controller, http.StatusBadRequest, "nothing to update", errors.New("no credentials provided"))
return
}

if err := h.etl.UpdateCredentials(h.Ctx.Request.Context(), userIDInt, req.Username, req.Password); err != nil {
utils.ErrorResponse(&h.Controller, http.StatusInternalServerError, fmt.Sprintf("failed to update credentials: %s", err), err)
return
}

utils.SuccessResponse(&h.Controller, "credentials updated successfully", nil)
}

// @Summary Get telemetry ID
// @Tags Internal
// @Description Retrieve the unique telemetry identifier and current UI version.
Expand Down
5 changes: 5 additions & 0 deletions server/internal/models/dto/requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ type LoginRequest struct {
Password string `json:"password" validate:"required" example:"password"`
}

type UpdateCredentialsRequest struct {
Username string `json:"username" validate:"required" example:"newadmin"`
Password string `json:"password" validate:"required" example:"newpassword"`
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can use the old struct also. just kept this for swagger and separation.


type SpecRequest struct {
// enum: postgres,mongodb,mysql,mssql,db2,s3,kafka,iceberg
Type string `json:"type" validate:"required" example:"postgres"`
Expand Down
27 changes: 27 additions & 0 deletions server/internal/services/etl/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,30 @@ func (s *ETLService) ValidateUser(userID int) error {
}
return nil
}

func (s *ETLService) UpdateCredentials(ctx context.Context, userID int, newUsername, newPassword string) error {
user, err := s.db.GetUserByID(userID)
if err != nil {
return fmt.Errorf("failed to load user: %s", err)
}

if newUsername != "" {
user.Username = newUsername
}

if newPassword != "" {
hashed, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
if err != nil {
return fmt.Errorf("failed to hash new password: %s", err)
}
user.Password = string(hashed)
}

if err := s.db.UpdateUser(user); err != nil {
return fmt.Errorf("failed to update credentials: %s", err)
}

telemetry.TrackUserUpdate(ctx, user)

return nil
}
1 change: 1 addition & 0 deletions server/routes/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ func Init(h *handlers.Handler) {
web.Router("/signup", h, "post:Signup")
web.Router("/auth/check", h, "get:CheckAuth")
web.Router("/telemetry-id", h, "get:GetTelemetryID")
web.Router("/user/credentials", h, "put:UpdateCredentials")

// User routes
web.Router("/api/v1/users", h, "post:CreateUser")
Expand Down
Loading
Loading