feat: support Remember Me for prolonging session validity on user/pass authentication (#52)
This commit is contained in:
committed by
GitHub
parent
473cd3f1ed
commit
873818ddac
@@ -29,6 +29,7 @@ c, err := unifi.NewClient(&unifi.ClientConfig{
|
||||
BaseURL: "https://unifi.localdomain",
|
||||
Username: "your-username",
|
||||
Password: "your-password",
|
||||
RememberMe: true, // Optional: prolong the session validity. Might be needed for long-running applications.
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Error creating client: %v", err)
|
||||
|
||||
149
docs/file_uploads.md
Normal file
149
docs/file_uploads.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# File Uploads in go-unifi
|
||||
|
||||
This document describes how to use the file upload functionality in the go-unifi client.
|
||||
|
||||
## Overview
|
||||
|
||||
The go-unifi client provides two methods for uploading files to the UniFi controller:
|
||||
|
||||
1. `UploadFile` - Upload a file from a file path on disk
|
||||
2. `UploadFileFromReader` - Upload a file from an `io.Reader` (e.g., from memory, network stream, etc.)
|
||||
|
||||
Both methods use the `multipart/form-data` format for file uploads, which is required by the UniFi controller.
|
||||
|
||||
## Examples
|
||||
|
||||
### Uploading a file from disk
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
"github.com/filipowm/go-unifi/unifi"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create a client
|
||||
client, err := unifi.NewClient(&unifi.ClientConfig{
|
||||
URL: "https://your-unifi-controller:8443",
|
||||
User: "your-username",
|
||||
Password: "your-password",
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Error creating client: %v", err)
|
||||
}
|
||||
|
||||
// Prepare any additional form fields if needed
|
||||
formFields := map[string]string{
|
||||
"description": "My uploaded file",
|
||||
}
|
||||
|
||||
// Upload the file to the controller
|
||||
var response map[string]interface{} // Adjust this type based on the expected response
|
||||
err = client.UploadFile(
|
||||
context.Background(),
|
||||
"/api/s/default/upload", // The API endpoint to upload to
|
||||
"/path/to/your/file.txt", // Path to the file on disk
|
||||
"file", // Form field name for the file
|
||||
formFields, // Additional form fields
|
||||
&response, // Response structure to capture the result
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("Error uploading file: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("Upload successful: %v", response)
|
||||
}
|
||||
```
|
||||
|
||||
### Uploading a file from memory
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"log"
|
||||
|
||||
"github.com/paultyng/go-unifi/unifi"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create a client
|
||||
client, err := unifi.NewClient(&unifi.ClientConfig{
|
||||
URL: "https://your-unifi-controller:8443",
|
||||
User: "your-username",
|
||||
Password: "your-password",
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Error creating client: %v", err)
|
||||
}
|
||||
|
||||
// Create file content in memory
|
||||
fileContent := []byte("This is some test content to upload")
|
||||
reader := bytes.NewReader(fileContent)
|
||||
|
||||
// Upload the file from the reader
|
||||
var response map[string]interface{} // Adjust this type based on the expected response
|
||||
err = client.UploadFileFromReader(
|
||||
context.Background(),
|
||||
"/api/s/default/upload", // The API endpoint to upload to
|
||||
reader, // Reader with the file content
|
||||
"myfile.txt", // Filename to use in the upload
|
||||
"file", // Form field name for the file
|
||||
nil, // No additional form fields
|
||||
&response, // Response structure to capture the result
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("Error uploading file: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("Upload successful: %v", response)
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### UploadFile
|
||||
|
||||
```go
|
||||
func (c *client) UploadFile(ctx context.Context, apiPath, filePath, fieldName string, formFields map[string]string, respBody interface{}) error
|
||||
```
|
||||
|
||||
Uploads a file to the UniFi controller from a file path.
|
||||
|
||||
Parameters:
|
||||
- `ctx`: The context for the request
|
||||
- `apiPath`: The API endpoint path to upload the file to
|
||||
- `filePath`: Path to the file on disk
|
||||
- `fieldName`: Form field name for the file (defaults to "file" if empty)
|
||||
- `formFields`: Additional form fields to include in the upload (can be nil)
|
||||
- `respBody`: Structure to decode the response into (can be nil)
|
||||
|
||||
### UploadFileFromReader
|
||||
|
||||
```go
|
||||
func (c *client) UploadFileFromReader(ctx context.Context, apiPath string, reader io.Reader, filename, fieldName string, formFields map[string]string, respBody interface{}) error
|
||||
```
|
||||
|
||||
Uploads a file to the UniFi controller from an io.Reader.
|
||||
|
||||
Parameters:
|
||||
- `ctx`: The context for the request
|
||||
- `apiPath`: The API endpoint path to upload the file to
|
||||
- `reader`: Reader with the file content
|
||||
- `filename`: Name of the file to use in the upload
|
||||
- `fieldName`: Form field name for the file (defaults to "file" if empty)
|
||||
- `formFields`: Additional form fields to include in the upload (can be nil)
|
||||
- `respBody`: Structure to decode the response into (can be nil)
|
||||
|
||||
## Notes
|
||||
|
||||
- These methods use `POST` requests for file uploads
|
||||
- The UniFi controller typically expects files to be uploaded with the field name "file", but this can be changed as needed
|
||||
- The content type for the request is automatically set to "multipart/form-data" with the correct boundary
|
||||
- All existing client features like interceptors, error handling, and request validation are preserved
|
||||
@@ -65,6 +65,20 @@ if err != nil {
|
||||
}
|
||||
```
|
||||
|
||||
You can also configure `Remember Me` option, which will prolong the session validity. Might be required for long-running applications, that require authentication only once.
|
||||
|
||||
```go
|
||||
c, err := unifi.NewClient(&unifi.ClientConfig{
|
||||
BaseURL: "https://unifi.localdomain",
|
||||
Username: "your-username",
|
||||
Password: "your-password",
|
||||
RememberMe: true,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create client: %v", err)
|
||||
}
|
||||
```
|
||||
|
||||
### Bare Client Initialization
|
||||
|
||||
You can also use bare client, which creates a `unifi.Client` without initialization like logging in and getting system information. This can be useful in specific scenarios, when doing such initialization might be an uneeded overhead. To create it you can use the `NewBareClient` function provided in the SDK (see `unifi/client.go`).
|
||||
|
||||
@@ -46,7 +46,8 @@ Fields:
|
||||
URL: The base URL of the UniFi controller. Must be a valid URL and should not include the `/api` suffix.
|
||||
APIKey: An API key used for authentication. Provide this if user/password credentials are not used.
|
||||
User: The username for user/password authentication. Must be provided with Password if APIKey is not used.
|
||||
Password: The password for user/password authentication. Must be provided with User if APIKey is not used.
|
||||
Password: The password for user/password authentication. Must be provided with User if APIKey is not used.
|
||||
RememberMe: If true, the session is remembered for future requests. Useful for long-running processes. Default: false. Only used for user/password authentication.
|
||||
Timeout: The maximum duration to wait for responses; default is no timeout.
|
||||
VerifySSL: When false, disables SSL certificate verification.
|
||||
Interceptors: A slice of ClientInterceptor implementations that can modify requests and responses.
|
||||
@@ -62,6 +63,7 @@ type ClientConfig struct {
|
||||
APIKey string `validate:"required_without_all=User Password"`
|
||||
User string `validate:"excluded_with=APIKey,required_with=Password"`
|
||||
Password string `validate:"excluded_with=APIKey,required_with=User"`
|
||||
RememberMe bool `validate:"excluded_with=APIKey"`
|
||||
Timeout time.Duration // How long to wait for replies, default: forever.
|
||||
VerifySSL bool
|
||||
Interceptors []ClientInterceptor
|
||||
@@ -85,6 +87,7 @@ type Credentials interface {
|
||||
GetUser() string
|
||||
// GetPass returns the password for authentication; returns an empty string if not applicable.
|
||||
GetPass() string
|
||||
IsRememberMe() bool
|
||||
}
|
||||
|
||||
// APIKeyCredentials holds API key authentication details.
|
||||
@@ -92,21 +95,24 @@ type APIKeyCredentials struct {
|
||||
APIKey string
|
||||
}
|
||||
|
||||
func (a APIKeyCredentials) IsAPIKey() bool { return true }
|
||||
func (a APIKeyCredentials) GetAPIKey() string { return a.APIKey }
|
||||
func (a APIKeyCredentials) GetUser() string { return "" }
|
||||
func (a APIKeyCredentials) GetPass() string { return "" }
|
||||
func (a APIKeyCredentials) IsAPIKey() bool { return true }
|
||||
func (a APIKeyCredentials) GetAPIKey() string { return a.APIKey }
|
||||
func (a APIKeyCredentials) GetUser() string { return "" }
|
||||
func (a APIKeyCredentials) GetPass() string { return "" }
|
||||
func (a APIKeyCredentials) IsRememberMe() bool { return false }
|
||||
|
||||
// UserPassCredentials holds user/password authentication.
|
||||
type UserPassCredentials struct {
|
||||
User string
|
||||
Password string
|
||||
Remember bool
|
||||
}
|
||||
|
||||
func (u UserPassCredentials) IsAPIKey() bool { return false }
|
||||
func (u UserPassCredentials) GetAPIKey() string { return "" }
|
||||
func (u UserPassCredentials) GetUser() string { return u.User }
|
||||
func (u UserPassCredentials) GetPass() string { return u.Password }
|
||||
func (u UserPassCredentials) IsAPIKey() bool { return false }
|
||||
func (u UserPassCredentials) GetAPIKey() string { return "" }
|
||||
func (u UserPassCredentials) GetUser() string { return u.User }
|
||||
func (u UserPassCredentials) GetPass() string { return u.Password }
|
||||
func (u UserPassCredentials) IsRememberMe() bool { return u.Remember }
|
||||
|
||||
// client represents a UniFi client.
|
||||
type client struct {
|
||||
@@ -219,7 +225,7 @@ func newClientFromConfig(config *ClientConfig, v *validator) (*client, error) {
|
||||
interceptors = append(interceptors, &APIKeyAuthInterceptor{apiKey: config.APIKey})
|
||||
} else {
|
||||
log.Debug("Using user/pass authentication")
|
||||
credentials = UserPassCredentials{User: config.User, Password: config.Password}
|
||||
credentials = UserPassCredentials{User: config.User, Password: config.Password, Remember: config.RememberMe}
|
||||
interceptors = append(interceptors, &CSRFInterceptor{})
|
||||
}
|
||||
if len(config.UserAgent) == 0 {
|
||||
@@ -322,9 +328,11 @@ func (c *client) Login() error {
|
||||
err := c.Post(ctx, c.apiPaths.LoginPath, &struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Remember bool `json:"remember"`
|
||||
}{
|
||||
Username: c.credentials.GetUser(),
|
||||
Password: c.credentials.GetPass(),
|
||||
Remember: c.credentials.IsRememberMe(),
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
Reference in New Issue
Block a user