diff --git a/src/api/api.go b/src/api/api.go index b8071bc..4d8307c 100644 --- a/src/api/api.go +++ b/src/api/api.go @@ -168,6 +168,15 @@ type RateLimitChallengeRequest struct { Captcha string `json:"captcha" example:"signalcaptcha://{captcha value}"` } +type UpdateAccountSettingsRequest struct { + DiscoverableByNumber *bool `json:"discoverable_by_number"` + ShareNumber *bool `json:"share_number"` +} + +type SetUsernameRequest struct { + Username string `json:username" example:"test"` +} + type Api struct { signalClient *client.SignalClient } @@ -1723,3 +1732,93 @@ func (a *Api) SubmitRateLimitChallenge(c *gin.Context) { } c.Status(http.StatusNoContent) } + +// @Summary Update the account settings. +// @Tags Accounts +// @Description Update the account attributes on the signal server. +// @Accept json +// @Produce json +// @Param number path string true "Registered Phone Number" +// @Param data body UpdateAccountSettingsRequest true "Request" +// @Success 204 +// @Failure 400 {object} Error +// @Router /v1/accounts/{number}/settings [put] +func (a *Api) UpdateAccountSettings(c *gin.Context) { + number := c.Param("number") + if number == "" { + c.JSON(400, Error{Msg: "Couldn't process request - number missing"}) + return + } + + var req UpdateAccountSettingsRequest + err := c.BindJSON(&req) + if err != nil { + c.JSON(400, Error{Msg: "Couldn't process request - invalid request"}) + return + } + + err = a.signalClient.UpdateAccountSettings(number, req.DiscoverableByNumber, req.ShareNumber) + if err != nil { + c.JSON(400, Error{Msg: err.Error()}) + return + } + + c.Status(201) +} + +// @Summary Set a username. +// @Tags Accounts +// @Description Allows to set the username that should be used for this account. This can either be just the nickname (e.g. test) or the complete username with discriminator (e.g. test.123). Returns the new username with discriminator and the username link. +// @Accept json +// @Produce json +// @Param number path string true "Registered Phone Number" +// @Param data body SetUsernameRequest true "Request" +// @Success 201 {object} client.SetUsernameResponse +// @Success 204 +// @Failure 400 {object} Error +// @Router /v1/accounts/{number}/username [post] +func (a *Api) SetUsername(c *gin.Context) { + number := c.Param("number") + if number == "" { + c.JSON(400, Error{Msg: "Couldn't process request - number missing"}) + return + } + + var req SetUsernameRequest + err := c.BindJSON(&req) + if err != nil { + c.JSON(400, Error{Msg: "Couldn't process request - invalid request"}) + return + } + + resp, err := a.signalClient.SetUsername(number, req.Username) + if err != nil { + c.JSON(400, Error{Msg: err.Error()}) + return + } + c.JSON(201, resp) +} + +// @Summary Remove a username. +// @Tags Accounts +// @Description Delete the username associated with this account. +// @Accept json +// @Produce json +// @Param number path string true "Registered Phone Number" +// @Success 204 +// @Failure 400 {object} Error +// @Router /v1/accounts/{number}/username [delete] +func (a *Api) RemoveUsername(c *gin.Context) { + number := c.Param("number") + if number == "" { + c.JSON(400, Error{Msg: "Couldn't process request - number missing"}) + return + } + + err := a.signalClient.RemoveUsername(number) + if err != nil { + c.JSON(400, Error{Msg: err.Error()}) + return + } + c.Status(http.StatusNoContent) +} diff --git a/src/client/client.go b/src/client/client.go index 2039e19..21a1dd6 100644 --- a/src/client/client.go +++ b/src/client/client.go @@ -177,6 +177,11 @@ type SearchResultEntry struct { Registered bool `json:"registered"` } +type SetUsernameResponse struct { + Username string `json:"username"` + UsernameLink string `json:"username_link"` +} + func cleanupTmpFiles(paths []string) { for _, path := range paths { os.Remove(path) @@ -1825,3 +1830,88 @@ func (s *SignalClient) SubmitRateLimitChallenge(number string, challengeToken st return err } } + +func (s *SignalClient) SetUsername(number string, username string) (SetUsernameResponse, error) { + type SetUsernameSignalCliResponse struct { + Username string `json:"username"` + UsernameLink string `json:"usernameLink"` + } + + var resp SetUsernameResponse + var err error + var rawData string + if s.signalCliMode == JsonRpc { + type Request struct { + Username string `json:"username"` + } + request := Request{Username: username} + jsonRpc2Client, err := s.getJsonRpc2Client() + if err != nil { + return resp, err + } + rawData, err = jsonRpc2Client.getRaw("updateAccount", &number, request) + } else { + cmd := []string{"--config", s.signalCliConfig, "-o", "json", "-a", number, "updateAccount", "-u", username} + rawData, err = s.cliClient.Execute(true, cmd, "") + } + + var signalCliResp SetUsernameSignalCliResponse + err = json.Unmarshal([]byte(rawData), &signalCliResp) + if err != nil { + return resp, errors.New("Couldn't process request - invalid signal-cli response") + } + + resp.Username = signalCliResp.Username + resp.UsernameLink = signalCliResp.UsernameLink + + return resp, err +} + +func (s *SignalClient) RemoveUsername(number string) error { + if s.signalCliMode == JsonRpc { + type Request struct { + DeleteUsername bool `json:"delete-username"` + } + request := Request{DeleteUsername: true} + jsonRpc2Client, err := s.getJsonRpc2Client() + if err != nil { + return err + } + _, err = jsonRpc2Client.getRaw("updateAccount", &number, request) + return err + } else { + cmd := []string{"--config", s.signalCliConfig, "-o", "json", "-a", number, "updateAccount", "--delete-username"} + _, err := s.cliClient.Execute(true, cmd, "") + return err + } +} + +func (s *SignalClient) UpdateAccountSettings(number string, discoverableByNumber *bool, shareNumber *bool) error { + if s.signalCliMode == JsonRpc { + type Request struct { + ShareNumber *bool `json:"number-sharing"` + DiscoverableByNumber *bool `json:"discoverable-by-number"` + } + request := Request{} + request.DiscoverableByNumber = discoverableByNumber + request.ShareNumber = shareNumber + + jsonRpc2Client, err := s.getJsonRpc2Client() + if err != nil { + return err + } + _, err = jsonRpc2Client.getRaw("updateAccount", &number, request) + return err + } else { + cmd := []string{"--config", s.signalCliConfig, "-a", number, "updateAccount"} + if discoverableByNumber != nil { + cmd = append(cmd, []string{"--discoverable-by-number", strconv.FormatBool(*discoverableByNumber)}...) + } + + if shareNumber != nil { + cmd = append(cmd, []string{"--number-sharing", strconv.FormatBool(*shareNumber)}...) + } + _, err := s.cliClient.Execute(true, cmd, "") + return err + } +} diff --git a/src/docs/docs.go b/src/docs/docs.go index fa1273d..35b7467 100644 --- a/src/docs/docs.go +++ b/src/docs/docs.go @@ -116,6 +116,127 @@ var doc = `{ } } }, + "/v1/accounts/{number}/settings": { + "put": { + "description": "Update the account attributes on the signal server.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Accounts" + ], + "summary": "Update the account settings.", + "parameters": [ + { + "type": "string", + "description": "Registered Phone Number", + "name": "number", + "in": "path", + "required": true + }, + { + "description": "Request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.UpdateAccountSettingsRequest" + } + } + ], + "responses": { + "204": {}, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.Error" + } + } + } + } + }, + "/v1/accounts/{number}/username": { + "post": { + "description": "Allows to set the username that should be used for this account. This can either be just the nickname (e.g. test) or the complete username with discriminator (e.g. test.123). Returns the new username with discriminator and the username link.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Accounts" + ], + "summary": "Set a username.", + "parameters": [ + { + "type": "string", + "description": "Registered Phone Number", + "name": "number", + "in": "path", + "required": true + }, + { + "description": "Request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.SetUsernameRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/client.SetUsernameResponse" + } + }, + "204": {}, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.Error" + } + } + } + }, + "delete": { + "description": "Delete the username associated with this account.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Accounts" + ], + "summary": "Remove a username.", + "parameters": [ + { + "type": "string", + "description": "Registered Phone Number", + "name": "number", + "in": "path", + "required": true + } + ], + "responses": { + "204": {}, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.Error" + } + } + } + } + }, "/v1/attachments": { "get": { "description": "List all downloaded attachments", @@ -1964,6 +2085,14 @@ var doc = `{ } } }, + "api.SetUsernameRequest": { + "type": "object", + "properties": { + "username": { + "type": "string" + } + } + }, "api.TrustIdentityRequest": { "type": "object", "properties": { @@ -2013,6 +2142,17 @@ var doc = `{ } } }, + "api.UpdateAccountSettingsRequest": { + "type": "object", + "properties": { + "discoverable_by_number": { + "type": "boolean" + }, + "share_number": { + "type": "boolean" + } + } + }, "api.UpdateContactRequest": { "type": "object", "properties": { @@ -2166,6 +2306,17 @@ var doc = `{ "type": "integer" } } + }, + "client.SetUsernameResponse": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "username_link": { + "type": "string" + } + } } }, "tags": [ diff --git a/src/docs/swagger.json b/src/docs/swagger.json index 7b72f61..8088c7b 100644 --- a/src/docs/swagger.json +++ b/src/docs/swagger.json @@ -100,6 +100,127 @@ } } }, + "/v1/accounts/{number}/settings": { + "put": { + "description": "Update the account attributes on the signal server.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Accounts" + ], + "summary": "Update the account settings.", + "parameters": [ + { + "type": "string", + "description": "Registered Phone Number", + "name": "number", + "in": "path", + "required": true + }, + { + "description": "Request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.UpdateAccountSettingsRequest" + } + } + ], + "responses": { + "204": {}, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.Error" + } + } + } + } + }, + "/v1/accounts/{number}/username": { + "post": { + "description": "Allows to set the username that should be used for this account. This can either be just the nickname (e.g. test) or the complete username with discriminator (e.g. test.123). Returns the new username with discriminator and the username link.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Accounts" + ], + "summary": "Set a username.", + "parameters": [ + { + "type": "string", + "description": "Registered Phone Number", + "name": "number", + "in": "path", + "required": true + }, + { + "description": "Request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.SetUsernameRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/client.SetUsernameResponse" + } + }, + "204": {}, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.Error" + } + } + } + }, + "delete": { + "description": "Delete the username associated with this account.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Accounts" + ], + "summary": "Remove a username.", + "parameters": [ + { + "type": "string", + "description": "Registered Phone Number", + "name": "number", + "in": "path", + "required": true + } + ], + "responses": { + "204": {}, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.Error" + } + } + } + } + }, "/v1/attachments": { "get": { "description": "List all downloaded attachments", @@ -1948,6 +2069,14 @@ } } }, + "api.SetUsernameRequest": { + "type": "object", + "properties": { + "username": { + "type": "string" + } + } + }, "api.TrustIdentityRequest": { "type": "object", "properties": { @@ -1997,6 +2126,17 @@ } } }, + "api.UpdateAccountSettingsRequest": { + "type": "object", + "properties": { + "discoverable_by_number": { + "type": "boolean" + }, + "share_number": { + "type": "boolean" + } + } + }, "api.UpdateContactRequest": { "type": "object", "properties": { @@ -2150,6 +2290,17 @@ "type": "integer" } } + }, + "client.SetUsernameResponse": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "username_link": { + "type": "string" + } + } } }, "tags": [ diff --git a/src/docs/swagger.yaml b/src/docs/swagger.yaml index ed7e087..1bf07d5 100644 --- a/src/docs/swagger.yaml +++ b/src/docs/swagger.yaml @@ -168,6 +168,11 @@ definitions: - styled type: string type: object + api.SetUsernameRequest: + properties: + username: + type: string + type: object api.TrustIdentityRequest: properties: trust_all_known_keys: @@ -200,6 +205,13 @@ definitions: example: false type: boolean type: object + api.UpdateAccountSettingsRequest: + properties: + discoverable_by_number: + type: boolean + share_number: + type: boolean + type: object api.UpdateContactRequest: properties: expiration_in_seconds: @@ -300,6 +312,13 @@ definitions: start: type: integer type: object + client.SetUsernameResponse: + properties: + username: + type: string + username_link: + type: string + type: object info: contact: {} description: This is the Signal Cli REST API documentation. @@ -367,6 +386,87 @@ paths: summary: Lift rate limit restrictions by solving a captcha. tags: - Accounts + /v1/accounts/{number}/settings: + put: + consumes: + - application/json + description: Update the account attributes on the signal server. + parameters: + - description: Registered Phone Number + in: path + name: number + required: true + type: string + - description: Request + in: body + name: data + required: true + schema: + $ref: '#/definitions/api.UpdateAccountSettingsRequest' + produces: + - application/json + responses: + "204": {} + "400": + description: Bad Request + schema: + $ref: '#/definitions/api.Error' + summary: Update the account settings. + tags: + - Accounts + /v1/accounts/{number}/username: + delete: + consumes: + - application/json + description: Delete the username associated with this account. + parameters: + - description: Registered Phone Number + in: path + name: number + required: true + type: string + produces: + - application/json + responses: + "204": {} + "400": + description: Bad Request + schema: + $ref: '#/definitions/api.Error' + summary: Remove a username. + tags: + - Accounts + post: + consumes: + - application/json + description: Allows to set the username that should be used for this account. This can either be just the nickname (e.g. test) or the complete username with discriminator (e.g. test.123). Returns the new username with discriminator and the username link. + parameters: + - description: Registered Phone Number + in: path + name: number + required: true + type: string + - description: Request + in: body + name: data + required: true + schema: + $ref: '#/definitions/api.SetUsernameRequest' + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/client.SetUsernameResponse' + "204": {} + "400": + description: Bad Request + schema: + $ref: '#/definitions/api.Error' + summary: Set a username. + tags: + - Accounts /v1/attachments: get: description: List all downloaded attachments diff --git a/src/main.go b/src/main.go index 6b05887..003a7da 100644 --- a/src/main.go +++ b/src/main.go @@ -205,6 +205,9 @@ func main() { { accounts.GET("", api.GetAccounts) accounts.POST(":number/rate-limit-challenge", api.SubmitRateLimitChallenge) + accounts.PUT(":number/settings", api.UpdateAccountSettings) + accounts.POST(":number/username", api.SetUsername) + accounts.DELETE(":number/username", api.RemoveUsername) } devices := v1.Group("devices")