3
0
mirror of https://github.com/ergochat/ergo.git synced 2026-02-07 01:48:06 +01:00
* Add API endpoint /ns/passwd to change passwords
* New naming scheme for API endpoints under /v1/ns/
* /v1/ns/list no longer returns error responses
This commit is contained in:
Shivaram Lingamneni 2026-02-02 19:26:37 +00:00
parent 2b1dad982f
commit a3ecfe94cf
2 changed files with 100 additions and 48 deletions

View File

@ -39,21 +39,6 @@ This returns:
Endpoints
=========
`/v1/account_details`
----------------
This endpoint fetches account details and returns them as JSON. The request is a JSON object with fields:
* `accountName`: string, name of the account
The response is a JSON object with fields:
* `success`: whether the account exists or not
* `accountName`: canonical, case-unfolded version of the account name
* `email`: email address of the account provided
* `registeredAt`: string, registration date/time of the account (in ISO8601 format)
* `channels`: array of strings, list of channels the account is registered on or associated with
`/v1/check_auth`
----------------
@ -67,16 +52,53 @@ The response is a JSON object with fields:
* `success`: whether the credentials provided were valid
* `accountName`: canonical, case-unfolded version of the account name
`/v1/rehash`
------------
`/v1/ns/info`
-------------
This endpoint rehashes the server (i.e. reloads the configuration file, TLS certificates, and other associated data). The body is ignored. The response is a JSON object with fields:
This endpoint fetches account details and returns them as JSON. The request is a JSON object with fields:
* `success`: boolean, indicates whether the rehash was successful
* `error`: string, optional, human-readable description of the failure
* `accountName`: string, name of the account
`/v1/saregister`
----------------
The response is a JSON object with fields:
* `success`: whether the account exists or not
* `accountName`: canonical, case-unfolded version of the account name
* `email`: email address of the account provided
* `registeredAt`: string, registration date/time of the account (in ISO8601 format)
* `channels`: array of strings, list of channels the account is registered on or associated with
Note: this endpoint was previously named `/v1/account_details`. The old name is still accepted for backwards compatibility.
`/v1/ns/list`
-------------
This endpoint fetches a list of all accounts. The request body is ignored and can be empty.
The response is a JSON object with fields:
* `success`: whether the request succeeded
* `accounts`: array of objects, each with fields:
* `success`: boolean, whether this individual account query succeeded
* `accountName`: string, canonical, case-unfolded version of the account name
* `totalCount`: integer, total number of accounts returned
Note: this endpoint was previously named `/v1/account_list`. The old name is still accepted for backwards compatibility.
`/v1/ns/passwd`
---------------
This endpoint changes the password of an existing NickServ account. The request is a JSON object with fields:
* `accountName`: string, name of the account
* `passphrase`: string, new passphrase for the account
The response is a JSON object with fields:
* `success`: whether the password change succeeded
* `errorCode`: string, optional, machine-readable description of the error. Possible values include: `ACCOUNT_DOES_NOT_EXIST`, `INVALID_PASSPHRASE`, `CREDENTIALS_EXTERNALLY_MANAGED`, `UNKNOWN_ERROR`.
`/v1/ns/saregister`
-------------------
This endpoint registers an account in NickServ, with the same semantics as `NS SAREGISTER`. The request is a JSON object with fields:
@ -89,22 +111,18 @@ The response is a JSON object with fields:
* `errorCode`: string, optional, machine-readable description of the error. Possible values include: `ACCOUNT_EXISTS`, `INVALID_PASSPHRASE`, `UNKNOWN_ERROR`.
* `error`: string, optional, human-readable description of the failure.
`/v1/account_list`
-------------------
Note: this endpoint was previously named `/v1/saregister`. The old name is still accepted for backwards compatibility.
This endpoint fetches a list of all accounts. The request body is ignored and can be empty.
`/v1/rehash`
------------
The response is a JSON object with fields:
* `success`: whether the request succeeded
* `accounts`: array of objects, each with fields:
* `success`: boolean, whether this individual account query succeeded
* `accountName`: string, canonical, case-unfolded version of the account name
* `totalCount`: integer, total number of accounts returned
This endpoint rehashes the server (i.e. reloads the configuration file, TLS certificates, and other associated data). The body is ignored. The response is a JSON object with fields:
* `success`: boolean, indicates whether the rehash was successful
* `error`: string, optional, human-readable description of the failure
`/v1/status`
-------------
------------
This endpoint returns status information about the running Ergo server. The request body is ignored and can be empty.

View File

@ -17,12 +17,23 @@ func newAPIHandler(server *Server) http.Handler {
mux: http.NewServeMux(),
}
// server-level functionality:
api.mux.HandleFunc("POST /v1/rehash", api.handleRehash)
api.mux.HandleFunc("POST /v1/status", api.handleStatus)
// use Ergo as a source of truth for authentication in other services:
api.mux.HandleFunc("POST /v1/check_auth", api.handleCheckAuth)
// legacy names for /v1/ns endpoints:
api.mux.HandleFunc("POST /v1/saregister", api.handleSaregister)
api.mux.HandleFunc("POST /v1/account_details", api.handleAccountDetails)
api.mux.HandleFunc("POST /v1/account_list", api.handleAccountList)
api.mux.HandleFunc("POST /v1/status", api.handleStatus)
// /v1/ns: nickserv functionality
api.mux.HandleFunc("POST /v1/ns/info", api.handleAccountDetails)
api.mux.HandleFunc("POST /v1/ns/list", api.handleAccountList)
api.mux.HandleFunc("POST /v1/ns/passwd", api.handleNsPasswd)
api.mux.HandleFunc("POST /v1/ns/saregister", api.handleSaregister)
return api
}
@ -174,6 +185,31 @@ func (a *ergoAPI) handleSaregister(w http.ResponseWriter, r *http.Request) {
a.writeJSONResponse(response, w, r)
}
func (a *ergoAPI) handleNsPasswd(w http.ResponseWriter, r *http.Request) {
var request apiSaregisterRequest
if err := a.decodeJSONRequest(&request, w, r); err != nil {
return
}
var response apiGenericResponse
err := a.server.accounts.setPassword(request.AccountName, request.Passphrase, true)
switch err {
case nil:
response.Success = true
case errAccountDoesNotExist:
response.ErrorCode = "ACCOUNT_DOES_NOT_EXIST"
case errAccountBadPassphrase, errEmptyCredentials:
response.ErrorCode = "INVALID_PASSPHRASE"
case errCredsExternallyManaged:
response.ErrorCode = "CREDENTIALS_EXTERNALLY_MANAGED"
default:
a.server.logger.Error("api", "could not change user password:", err.Error())
response.ErrorCode = "UNKNOWN_ERROR"
}
a.writeJSONResponse(response, w, r)
}
type apiAccountDetailsResponse struct {
apiGenericResponse
AccountName string `json:"accountName,omitempty"`
@ -244,26 +280,24 @@ func (a *ergoAPI) handleAccountList(w http.ResponseWriter, r *http.Request) {
response.TotalCount = len(accounts)
// Load account details
response.Accounts = make([]apiAccountDetailsResponse, len(accounts))
for i, account := range accounts {
response.Accounts = make([]apiAccountDetailsResponse, 0, len(accounts))
for _, account := range accounts {
accountData, err := a.server.accounts.LoadAccount(account)
if err != nil {
response.Accounts[i] = apiAccountDetailsResponse{
apiGenericResponse: apiGenericResponse{
Success: false,
Error: err.Error(),
},
}
// shouldn't happen
continue
}
response.Accounts[i] = apiAccountDetailsResponse{
apiGenericResponse: apiGenericResponse{
Success: true,
response.Accounts = append(
response.Accounts,
apiAccountDetailsResponse{
apiGenericResponse: apiGenericResponse{
Success: true,
},
AccountName: accountData.Name,
Email: accountData.Settings.Email,
},
AccountName: accountData.Name,
Email: accountData.Settings.Email,
}
)
}
response.Success = true