diff --git a/src/api/api.go b/src/api/api.go index 47b00a6..81e110e 100644 --- a/src/api/api.go +++ b/src/api/api.go @@ -37,6 +37,14 @@ type GroupEntry struct { Blocked bool `json:"blocked"` } +type IdentityEntry struct { + Number string `json:"number"` + Status string `json:"status"` + Fingerprint string `json:"fingerprint"` + Added string `json:"added"` + SafetyNumber string `json:"safety_number"` +} + type RegisterNumberRequest struct { UseVoice bool `json:"use_voice"` Captcha string `json:"captcha"` @@ -79,6 +87,10 @@ type UpdateProfileRequest struct { Base64Avatar string `json:"base64_avatar"` } +type TrustIdentityRequest struct { + VerifiedSafetyNumber string `json:"verified_safety_number"` +} + func convertInternalGroupIdToGroupId(internalId string) string { return groupPrefix + base64.StdEncoding.EncodeToString([]byte(internalId)) } @@ -192,6 +204,36 @@ func send(c *gin.Context, attachmentTmpDir string, signalCliConfig string, numbe c.JSON(201, nil) } +func parseWhitespaceDelimitedKeyValueStringList(in string, keys []string) []map[string]string { + l := []map[string]string{} + lines := strings.Split(in, "\n") + for _, line := range lines { + if line == "" { + continue + } + + m := make(map[string]string) + + temp := line + for i, key := range keys { + if i == 0 { + continue + } + + idx := strings.Index(temp, " " + key + ": ") + pair := temp[:idx] + value := strings.TrimPrefix(pair, key + ": ") + temp = strings.TrimLeft(temp[idx:], " "+key+": ") + + m[keys[i-1]] = value + } + m[keys[len(keys)-1]] = temp + + l = append(l, m) + } + return l +} + func getGroups(number string, signalCliConfig string) ([]GroupEntry, error) { groupEntries := []GroupEntry{} @@ -789,6 +831,11 @@ func (a *Api) ServeAttachment(c *gin.Context) { func (a *Api) UpdateProfile(c *gin.Context) { number := c.Param("number") + if number == "" { + c.JSON(400, Error{Msg: "Couldn't process request - number missing"}) + return + } + var req UpdateProfileRequest err := c.BindJSON(&req) if err != nil { @@ -870,3 +917,88 @@ func (a *Api) UpdateProfile(c *gin.Context) { func (a *Api) Health(c *gin.Context) { c.Status(http.StatusNoContent) } + +// @Summary List Identities +// @Tags Identities +// @Description List all identities for the given number. +// @Produce json +// @Success 200 {object} []IdentityEntry +// @Param number path string true "Registered Phone Number" +// @Router /v1/identities/{number} [get] +func (a *Api) ListIdentities(c *gin.Context) { + number := c.Param("number") + + if number == "" { + c.JSON(400, Error{Msg: "Couldn't process request - number missing"}) + return + } + + out, err := runSignalCli(true, []string{"--config", a.signalCliConfig, "-u", number, "listIdentities"}, "") + if err != nil { + c.JSON(500, Error{Msg: err.Error()}) + return + } + + identityEntries := []IdentityEntry{} + keyValuePairs := parseWhitespaceDelimitedKeyValueStringList(out, []string{"NumberAndTrustStatus", "Added", "Fingerprint", "Safety Number"}) + for _, keyValuePair := range keyValuePairs { + numberAndTrustStatus := keyValuePair["NumberAndTrustStatus"] + numberAndTrustStatusSplitted := strings.Split(numberAndTrustStatus, ":") + + + identityEntry := IdentityEntry{Number: strings.Trim(numberAndTrustStatusSplitted[0], " "), + Status: strings.Trim(numberAndTrustStatusSplitted[1], " "), + Added: keyValuePair["Added"], + Fingerprint: strings.Trim(keyValuePair["Fingerprint"], " "), + SafetyNumber: strings.Trim(keyValuePair["Safety Number"], " "), + } + identityEntries = append(identityEntries, identityEntry) + } + + c.JSON(200, identityEntries) +} + +// @Summary Trust Identity +// @Tags Identities +// @Description Trust an identity. +// @Produce json +// @Success 204 {string} OK +// @Param data body TrustIdentityRequest true "Input Data" +// @Param number path string true "Registered Phone Number" +// @Param numberToTrust path string true "Number To Trust" +// @Router /v1/identities/{number}/{numberToTrust} [put] +func (a *Api) TrustIdentity(c *gin.Context) { + number := c.Param("number") + + if number == "" { + c.JSON(400, Error{Msg: "Couldn't process request - number missing"}) + return + } + + numberToTrust := c.Param("numbertotrust") + if numberToTrust == "" { + c.JSON(400, Error{Msg: "Couldn't process request - number to trust missing"}) + return + } + + var req TrustIdentityRequest + err := c.BindJSON(&req) + if err != nil { + c.JSON(400, Error{Msg: "Couldn't process request - invalid request"}) + log.Error(err.Error()) + return + } + + if req.VerifiedSafetyNumber == "" { + c.JSON(400, Error{Msg: "Couldn't process request - verified safety number missing"}) + return + } + + cmd := []string{"--config", a.signalCliConfig, "-u", number, "trust", numberToTrust, "--verified-safety-number", req.VerifiedSafetyNumber} + _, err = runSignalCli(true, cmd, "") + if err != nil { + c.JSON(400, Error{Msg: err.Error()}) + return + } + c.Status(http.StatusNoContent) +} diff --git a/src/docs/docs.go b/src/docs/docs.go index 8382bb8..99b5122 100644 --- a/src/docs/docs.go +++ b/src/docs/docs.go @@ -284,6 +284,83 @@ var doc = `{ } } }, + "/v1/identities/{number}": { + "get": { + "description": "List all identities for the given number.", + "produces": [ + "application/json" + ], + "tags": [ + "Identities" + ], + "summary": "List Identities", + "parameters": [ + { + "type": "string", + "description": "Registered Phone Number", + "name": "number", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/api.IdentityEntry" + } + } + } + } + } + }, + "/v1/identities/{number}/{numberToTrust}": { + "put": { + "description": "Trust an identity.", + "produces": [ + "application/json" + ], + "tags": [ + "Identities" + ], + "summary": "Trust Identity", + "parameters": [ + { + "description": "Input Data", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.TrustIdentityRequest" + } + }, + { + "type": "string", + "description": "Registered Phone Number", + "name": "number", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Number To Trust", + "name": "numberToTrust", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content", + "schema": { + "type": "string" + } + } + } + } + }, "/v1/profiles/{number}": { "put": { "description": "Set your name and optional an avatar.", @@ -627,6 +704,26 @@ var doc = `{ } } }, + "api.IdentityEntry": { + "type": "object", + "properties": { + "added": { + "type": "string" + }, + "fingerprint": { + "type": "string" + }, + "number": { + "type": "string" + }, + "safety_number": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, "api.SendMessageV1": { "type": "object", "properties": { @@ -673,6 +770,14 @@ var doc = `{ } } }, + "api.TrustIdentityRequest": { + "type": "object", + "properties": { + "verified_safety_number": { + "type": "string" + } + } + }, "api.UpdateProfileRequest": { "type": "object", "properties": { @@ -717,6 +822,10 @@ var doc = `{ { "description": "Update Profile.", "name": "Profiles" + }, + { + "description": "List and Trust Identities.", + "name": "Identities" } ] }` diff --git a/src/docs/swagger.json b/src/docs/swagger.json index 3b714b5..4cdd0b0 100644 --- a/src/docs/swagger.json +++ b/src/docs/swagger.json @@ -269,6 +269,83 @@ } } }, + "/v1/identities/{number}": { + "get": { + "description": "List all identities for the given number.", + "produces": [ + "application/json" + ], + "tags": [ + "Identities" + ], + "summary": "List Identities", + "parameters": [ + { + "type": "string", + "description": "Registered Phone Number", + "name": "number", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/api.IdentityEntry" + } + } + } + } + } + }, + "/v1/identities/{number}/{numberToTrust}": { + "put": { + "description": "Trust an identity.", + "produces": [ + "application/json" + ], + "tags": [ + "Identities" + ], + "summary": "Trust Identity", + "parameters": [ + { + "description": "Input Data", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.TrustIdentityRequest" + } + }, + { + "type": "string", + "description": "Registered Phone Number", + "name": "number", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Number To Trust", + "name": "numberToTrust", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content", + "schema": { + "type": "string" + } + } + } + } + }, "/v1/profiles/{number}": { "put": { "description": "Set your name and optional an avatar.", @@ -612,6 +689,26 @@ } } }, + "api.IdentityEntry": { + "type": "object", + "properties": { + "added": { + "type": "string" + }, + "fingerprint": { + "type": "string" + }, + "number": { + "type": "string" + }, + "safety_number": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, "api.SendMessageV1": { "type": "object", "properties": { @@ -658,6 +755,14 @@ } } }, + "api.TrustIdentityRequest": { + "type": "object", + "properties": { + "verified_safety_number": { + "type": "string" + } + } + }, "api.UpdateProfileRequest": { "type": "object", "properties": { @@ -702,6 +807,10 @@ { "description": "Update Profile.", "name": "Profiles" + }, + { + "description": "List and Trust Identities.", + "name": "Identities" } ] } \ No newline at end of file diff --git a/src/docs/swagger.yaml b/src/docs/swagger.yaml index 52f3210..f96f1ec 100644 --- a/src/docs/swagger.yaml +++ b/src/docs/swagger.yaml @@ -36,6 +36,19 @@ definitions: name: type: string type: object + api.IdentityEntry: + properties: + added: + type: string + fingerprint: + type: string + number: + type: string + safety_number: + type: string + status: + type: string + type: object api.SendMessageV1: properties: base64_attachment: @@ -66,6 +79,11 @@ definitions: type: string type: array type: object + api.TrustIdentityRequest: + properties: + verified_safety_number: + type: string + type: object api.UpdateProfileRequest: properties: base64_avatar: @@ -257,6 +275,57 @@ paths: summary: API Health Check tags: - General + /v1/identities/{number}: + get: + description: List all identities for the given number. + parameters: + - description: Registered Phone Number + in: path + name: number + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/api.IdentityEntry' + type: array + summary: List Identities + tags: + - Identities + /v1/identities/{number}/{numberToTrust}: + put: + description: Trust an identity. + parameters: + - description: Input Data + in: body + name: data + required: true + schema: + $ref: '#/definitions/api.TrustIdentityRequest' + - description: Registered Phone Number + in: path + name: number + required: true + type: string + - description: Number To Trust + in: path + name: numberToTrust + required: true + type: string + produces: + - application/json + responses: + "204": + description: No Content + schema: + type: string + summary: Trust Identity + tags: + - Identities /v1/profiles/{number}: put: description: Set your name and optional an avatar. @@ -459,3 +528,5 @@ tags: name: Attachments - description: Update Profile. name: Profiles +- description: List and Trust Identities. + name: Identities diff --git a/src/main.go b/src/main.go index a60a664..4a2bbb7 100644 --- a/src/main.go +++ b/src/main.go @@ -37,6 +37,9 @@ import ( // @tag.name Profiles // @tag.description Update Profile. +// @tag.name Identities +// @tag.description List and Trust Identities. + // @host 127.0.0.1:8080 // @BasePath / func main() { @@ -106,6 +109,12 @@ func main() { { profiles.PUT(":number", api.UpdateProfile) } + + identities := v1.Group("identities") + { + identities.GET(":number", api.ListIdentities) + identities.PUT(":number/trust/:numbertotrust", api.TrustIdentity) + } } v2 := router.Group("/v2")