Skip to content

Commit 67b0d25

Browse files
committed
feat: add authentication
Also remove extension/matrix mapping: it's now automatic after auth
1 parent eb22a9f commit 67b0d25

File tree

10 files changed

+656
-259
lines changed

10 files changed

+656
-259
lines changed

README.md

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ The service authenticates to the Matrix homeserver as an **Application Service**
1212

1313
The proxy is configured via environment variables. Minimal required env:
1414

15-
- `MATRIX_HOMESERVER_URL`: URL of your Matrix homeserver (e.g. `https://matrix.example`)
15+
- `MATRIX_HOMESERVER_URL`: URL of your Matrix homeserver (e.g. `https://matrix.example`),
16+
used also to derive the hostname when constructing Matrix IDs from external auth responses
1617
- `SUPER_ADMIN_TOKEN`: the Application Service `as_token` from your registration file
1718
- `PROXY_PORT` (optional): port to listen on (default: `8080`)
1819
- `AS_USER_ID` (optional): the user ID of the Application Service bot (default: `@_acrobits_proxy:matrix.example`)
1920
- `PROXY_URL` (optional): public-facing URL of this proxy (e.g. `https://matrix-proxy.example.com`) - **required for push notification support**
21+
- `EXT_AUTH_URL` (optional): external HTTP endpoint used to validate extension+password for push token reports (default: `https://voice.gs.nethserver.net/freepbx/testextauth`)
22+
- `EXT_AUTH_TIMEOUT_S` (optional): timeout in seconds for calls to `EXT_AUTH_URL` (default: `5`)
2023
- `LOGLEVEL` (optional): logging verbosity level - `DEBUG`, `INFO`, `WARNING`, `CRITICAL` (default: `INFO`)
21-
- `MAPPING_FILE` (optional): path to a JSON file containing -to-Matrix mappings to load at startup
2224
- `PUSH_TOKEN_DB_PATH` (optional): path to a database file for storing push tokens
2325
- `CACHE_TTL_SECONDS` (optional): time-to-live for in-memory cache entries (default: `3600` seconds)
2426

@@ -49,20 +51,7 @@ The `LOGLEVEL` environment variable controls the verbosity of application logs:
4951

5052
For debugging mapping and API issues, set `LOGLEVEL=DEBUG` to see detailed trace information.
5153

52-
### Loading Mappings from File
5354

54-
You can pre-load -to-Matrix mappings at startup by providing a `MAPPING_FILE` environment variable pointing to a JSON file. This is useful for initializing the proxy with a set of known mappings.
55-
56-
See `docs/example-mappings.json` for an example format.
57-
58-
Usage:
59-
60-
```bash
61-
export MAPPING_FILE="/path/to/mappings.json"
62-
./matrix2acrobits
63-
```
64-
65-
The loaded mappings will be logged at startup with the message: `mappings loaded from file count=N file=/path/to/mappings.json`
6655

6756
## Extra info
6857

@@ -82,10 +71,52 @@ Implemented APIs:
8271
- https://doc.acrobits.net/api/client/send_message.html
8372
- https://doc.acrobits.net/api/client/push_token_reporter.html
8473

74+
## Authentication
75+
76+
All client API endpoints (`/api/client/fetch_messages`, `/api/client/send_message`, `/api/client/push_token_report`) require authentication via an external authentication service.
77+
78+
### External Auth Flow
79+
80+
When a client sends a request, the proxy:
81+
1. Extracts the `username` (extension) and `password` from the request.
82+
2. Calls `EXT_AUTH_URL` with a POST request containing JSON: `{"extension":"<username>","secret":"<password>"}`.
83+
3. On successful auth (200), parses the response for `main_extension`, `sub_extensions`, and `user_name`, which are converted into a mapping and saved.
84+
4. On failure (401 or other error), returns an authentication error and does NOT save the push token or create a mapping.
85+
5. Auth responses are cached in-memory for `CACHE_TTL_SECONDS` seconds to reduce external service load.
86+
87+
### Request Requirements
88+
89+
- **`/api/client/fetch_messages`**: Requires `username` and `password` fields.
90+
- **`/api/client/send_message`**: Requires `from` (sender extension/ID) and `password` fields.
91+
- **`/api/client/push_token_report`**: Requires `username` (extension) and `password` fields.
92+
93+
If any request is missing a `password`, it fails with authentication error.
94+
8595
## TODO
8696

8797
The following features are not yet implemented:
8898

89-
- sendMessage: implement password validation on send messages, currently the password is ignored
9099
- when a private room is deleted, there is no way to send messages to the user
91-
- implement https://doc.acrobits.net/api/client/account_removal_reporter.html#account-removal-reporter-webservice
100+
- implement https://doc.acrobits.net/api/client/account_removal_reporter.html#account-removal-reporter-webservice
101+
102+
Example minimal push token report request body (JSON):
103+
104+
```json
105+
{
106+
"username": "91201",
107+
"password": "user-password",
108+
"selector": "@alice:example.com",
109+
"token_msgs": "token123",
110+
"app_id_msgs": "com.acrobits.softphone"
111+
}
112+
```
113+
114+
Environment example for running with external auth and Matrix host:
115+
116+
```bash
117+
export MATRIX_HOMESERVER_URL="https://matrix.example.com"
118+
export EXT_AUTH_URL="https://voice.gs.nethserver.net/freepbx/testextauth"
119+
export EXT_AUTH_TIMEOUT_S=5
120+
export CACHE_TTL_SECONDS=3600
121+
./matrix2acrobits
122+
```

agents.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,13 @@ export LOGLEVEL=INFO # DEBUG, INFO, WARNING, or CRITICAL
101101

102102
### Unit Tests
103103

104-
- Test `internal/service` logic using mocks for the Matrix client.
104+
- Test logic using mocks for the Matrix client.
105105
- Verify correct JSON marshalling/unmarshalling of Acrobits payloads.
106106
- Verify mapping logic (e.g., Matrix timestamp → Acrobits RFC3339).
107+
- Execute unit tests:
108+
```
109+
go test -v ./...
110+
```
107111

108112
### Integration Tests
109113

api/routes.go

Lines changed: 0 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@ func RegisterRoutes(e *echo.Echo, svc *service.MessageService, pushSvc *service.
2121
e.POST("/api/client/send_message", h.sendMessage)
2222
e.POST("/api/client/fetch_messages", h.fetchMessages)
2323
e.POST("/api/client/push_token_report", h.pushTokenReport)
24-
e.POST("/api/internal/map_number_to_matrix", h.postMapping)
25-
e.GET("/api/internal/map_number_to_matrix", h.getMapping)
2624
e.GET("/api/internal/push_tokens", h.getPushTokens)
2725
e.DELETE("/api/internal/push_tokens", h.resetPushTokens)
2826

@@ -99,63 +97,6 @@ func (h handler) pushTokenReport(c echo.Context) error {
9997
return c.JSON(http.StatusOK, resp)
10098
}
10199

102-
func (h handler) postMapping(c echo.Context) error {
103-
if err := h.ensureAdminAccess(c); err != nil {
104-
return err
105-
}
106-
107-
var req models.MappingRequest
108-
if err := c.Bind(&req); err != nil {
109-
logger.Warn().Str("endpoint", "post_mapping").Err(err).Msg("invalid request payload")
110-
return echo.NewHTTPError(http.StatusBadRequest, "invalid payload")
111-
}
112-
113-
logger.Debug().Str("endpoint", "post_mapping").Int("number", req.Number).Msg("saving mapping")
114-
115-
resp, err := h.svc.SaveMapping(&req)
116-
if err != nil {
117-
logger.Error().Str("endpoint", "post_mapping").Int("number", req.Number).Err(err).Msg("failed to save mapping")
118-
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
119-
}
120-
121-
logger.Info().Str("endpoint", "post_mapping").Int("number", req.Number).Msg("mapping saved successfully")
122-
return c.JSON(http.StatusOK, resp)
123-
}
124-
125-
func (h handler) getMapping(c echo.Context) error {
126-
if err := h.ensureAdminAccess(c); err != nil {
127-
return err
128-
}
129-
130-
number := strings.TrimSpace(c.QueryParam("number"))
131-
if number == "" {
132-
logger.Debug().Str("endpoint", "get_mapping").Msg("listing all mappings")
133-
// return full mappings list when number is not provided
134-
respList, err := h.svc.ListMappings()
135-
if err != nil {
136-
logger.Error().Str("endpoint", "get_mapping").Err(err).Msg("failed to list mappings")
137-
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
138-
}
139-
logger.Info().Str("endpoint", "get_mapping").Int("count", len(respList)).Msg("listed all mappings")
140-
return c.JSON(http.StatusOK, respList)
141-
}
142-
143-
logger.Debug().Str("endpoint", "get_mapping").Str("number", number).Msg("looking up mapping")
144-
145-
resp, err := h.svc.LookupMapping(number)
146-
if err != nil {
147-
if errors.Is(err, service.ErrMappingNotFound) {
148-
logger.Warn().Str("endpoint", "get_mapping").Str("number", number).Msg("mapping not found")
149-
return echo.NewHTTPError(http.StatusNotFound, err.Error())
150-
}
151-
logger.Error().Str("endpoint", "get_mapping").Str("number", number).Err(err).Msg("failed to lookup mapping")
152-
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
153-
}
154-
155-
logger.Info().Str("endpoint", "get_mapping").Str("number", number).Msg("mapping found")
156-
return c.JSON(http.StatusOK, resp)
157-
}
158-
159100
func (h handler) getPushTokens(c echo.Context) error {
160101
if err := h.ensureAdminAccess(c); err != nil {
161102
return err

docs/example-mappings.json

Lines changed: 0 additions & 12 deletions
This file was deleted.

0 commit comments

Comments
 (0)