Skip to content

Latest commit

 

History

History
366 lines (264 loc) · 9.29 KB

File metadata and controls

366 lines (264 loc) · 9.29 KB

API Reference

JSON-RPC API

Endpoint

POST /api/rpc
Content-Type: application/json

Request Format

{
  "method": string,  // RPC method name
  "params": string[] // Method parameters
}

Response Format

{
  "success": boolean,  // Operation success status
  "data": string[]     // Response data or error messages
}

Available Methods

change-password

Changes a user's password in the LDAP/ActiveDirectory server.

Request

{
  "method": "change-password",
  "params": [
    "username", // sAMAccountName
    "currentPassword", // Current password for authentication
    "newPassword" // New password to set
  ]
}

Successful Response

{
  "success": true,
  "data": ["password changed successfully"]
}

HTTP Status: 200 OK

Error Responses

Validation Errors
{
  "success": false,
  "data": ["error message"]
}

HTTP Status: 500 Internal Server Error

Common Validation Errors:

  • "the username can't be empty"
  • "the old password can't be empty"
  • "the new password can't be empty"
  • "the old password can't be same as the new one"
  • "the new password must be at least {N} characters long"
  • "the new password must contain at least {N} number(s)"
  • "the new password must contain at least {N} symbol(s)"
  • "the new password must contain at least {N} uppercase letter(s)"
  • "the new password must contain at least {N} lowercase letter(s)"
  • "the new password must not include the username"
LDAP Errors
{
  "success": false,
  "data": ["LDAP error message from simple-ldap-go"]
}

HTTP Status: 500 Internal Server Error

Common LDAP Errors:

  • Authentication failures (incorrect current password)
  • User not found in LDAP directory
  • Password policy violations from AD/LDAP server
  • Connection errors to LDAP server
Invalid Method
{
  "success": false,
  "data": ["method not found"]
}

HTTP Status: 400 Bad Request

Implementation Details

Backend Validation Flow

Location: internal/rpchandler/change_password.go:18-72

func (c *Handler) changePassword(params []string) ([]string, error) {
  // 1. Validate parameter count
  if len(params) != 3 {
    return nil, ErrInvalidArgumentCount
  }

  // 2. Extract parameters
  sAMAccountName := params[0]
  currentPassword := params[1]
  newPassword := params[2]

  // 3. Empty field validation
  // 4. Password match validation
  // 5. Length validation (c.opts.MinLength)
  // 6. Number validation (validators.MinNumbersInString)
  // 7. Symbol validation (validators.MinSymbolsInString)
  // 8. Uppercase validation (validators.MinUppercaseLettersInString)
  // 9. Lowercase validation (validators.MinLowercaseLettersInString)
  // 10. Username inclusion check (optional, based on c.opts.PasswordCanIncludeUsername)
  // 11. LDAP password change operation

  return []string{"password changed successfully"}, nil
}

Frontend Implementation

Location: internal/web/static/js/app.ts:134-188

form.onsubmit = async (e) => {
  e.preventDefault();

  // 1. Collect form values
  const [username, oldPassword, newPassword] = fields.map((f) => f.getValue());

  // 2. Validate all fields
  const hasErrors = fields.map(({ validate }) => validate()).some((e) => e === true);
  if (hasErrors) return;

  // 3. Disable form during submission
  toggleFields(false);

  // 4. Make RPC request
  const res = await fetch("/api/rpc", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      method: "change-password",
      params: [username, oldPassword, newPassword]
    })
  });

  // 5. Handle response
  if (!res.ok) {
    // Display error
  } else {
    // Show success state
    form.style.display = "none";
    successContainer.style.display = "block";
  }
};

Validation Rules

Password Requirements

All validation rules are configurable via environment variables or command-line flags:

Requirement Config Variable Default Backend Validator Frontend Validator
Minimum Length MIN_LENGTH / --min-length 8 len(password) >= minLength mustBeLongerThan(n)
Minimum Numbers MIN_NUMBERS / --min-numbers 1 MinNumbersInString mustIncludeNumbers(n)
Minimum Symbols MIN_SYMBOLS / --min-symbols 1 MinSymbolsInString mustIncludeSymbols(n)
Minimum Uppercase MIN_UPPERCASE / --min-uppercase 1 MinUppercaseLettersInString mustIncludeUppercase(n)
Minimum Lowercase MIN_LOWERCASE / --min-lowercase 1 MinLowercaseLettersInString mustIncludeLowercase(n)
Username Exclusion PASSWORD_CAN_INCLUDE_USERNAME / --password-can-include-username false !strings.Contains(username, password) mustNotIncludeUsername (conditional)

Symbol Character Set

ASCII Ranges (matching implementation in validators.go:14-23 and validators.ts:1-23):

  • ! to / (ASCII 33-47): ! " # $ % & ' ( ) * + , - . /
  • : to @ (ASCII 58-64): : ; < = > ? @
  • [ to ` (ASCII 91-96): `[ \ ] ^ _ ``
  • { to ~ (ASCII 123-126): { | } ~

Total Special Characters: 32 symbols

Cross-Field Validations

Frontend Only:

  • mustMatchNewPassword: Ensures password confirmation matches new password
  • mustNotMatchCurrentPassword: Prevents reusing current password

Backend:

  • Implicitly validates via LDAP authentication (current password must be correct)

Error Handling

Client-Side Error Display

Location: internal/web/static/js/app.ts:76-91

Errors are displayed below each input field in real-time:

  • Red border around input container
  • Error messages in red text (text-xs text-red-400)
  • Submit button disabled while errors exist

Server-Side Error Response

Location: internal/rpchandler/handler.go:33-46

All errors are wrapped in consistent JSON-RPC response format:

return c.Status(http.StatusInternalServerError).JSON(JSONRPCResponse{
  Success: false,
  Data:    []string{err.Error()},
})

Security Considerations

Authentication

  • Current password required for all password change operations
  • LDAP server performs authentication (no password storage in application)
  • Password transmitted via HTTPS (enforced by LDAPS requirement)

Authorization

  • Users can only change their own password
  • Readonly LDAP user has minimal permissions (read-only access)
  • No administrative privileges exposed via API

Input Validation

  • Request body size limited to 4KB (main.go:31)
  • All parameters validated before LDAP operation
  • No SQL injection risk (LDAP-based, not SQL)
  • XSS protection via proper input handling

Transport Security

  • LDAPS (LDAP over SSL/TLS) required for ActiveDirectory
  • HTTPS recommended for web frontend
  • Credentials never logged or stored

Performance Considerations

Compression

Location: main.go:34-36

All responses compressed with Brotli:

app.Use(compress.New(compress.Config{
  Level: compress.LevelBestSpeed,
}))

Caching

Location: main.go:38-41

Static assets cached for 24 hours:

app.Use("/static", filesystem.New(filesystem.Config{
  Root:   http.FS(static.Static),
  MaxAge: 24 * 60 * 60,
}))

Body Limits

Location: main.go:29-32

Request size limited to prevent abuse:

app := fiber.New(fiber.Config{
  BodyLimit: 4 * 1024, // 4KB
})

Testing

Manual Testing

# Successful password change
curl -X POST http://localhost:3000/api/rpc \
  -H "Content-Type: application/json" \
  -d '{
    "method": "change-password",
    "params": ["testuser", "OldPass123!", "NewPass456!"]
  }'

# Expected response:
# {"success":true,"data":["password changed successfully"]}

# Validation error (password too short)
curl -X POST http://localhost:3000/api/rpc \
  -H "Content-Type: application/json" \
  -d '{
    "method": "change-password",
    "params": ["testuser", "OldPass123!", "short"]
  }'

# Expected response:
# {"success":false,"data":["the new password must be at least 8 characters long"]}

# Method not found
curl -X POST http://localhost:3000/api/rpc \
  -H "Content-Type: application/json" \
  -d '{
    "method": "invalid-method",
    "params": []
  }'

# Expected response:
# {"success":false,"data":["method not found"]}

Integration Testing

No integration tests currently implemented. See Testing Guide for recommendations.

Related Documentation


Generated by /sc:index on 2025-10-04