Skip to content

Commit 83be26f

Browse files
committed
fix(mcp): harden OAuth and message reads
Bound unauthenticated client registration, revoke refresh-token families on reuse, and restore server-side incoming-message pagination. Align Firebase certificate parsing and preserve contact details in tool output. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9ff1e38a-b018-4cf7-a5e9-5044a2efd03c
1 parent c2556b3 commit 83be26f

35 files changed

Lines changed: 2495 additions & 155 deletions

‎api/docs/docs.go‎

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1571,6 +1571,107 @@ const docTemplate = `{
15711571
}
15721572
}
15731573
},
1574+
"/messages/incoming": {
1575+
"get": {
1576+
"security": [
1577+
{
1578+
"ApiKeyAuth": []
1579+
}
1580+
],
1581+
"description": "This returns the list of mobile-originated messages received by the user's phones. This route is scoped to messages:read and never returns other message types",
1582+
"consumes": [
1583+
"application/json"
1584+
],
1585+
"produces": [
1586+
"application/json"
1587+
],
1588+
"tags": [
1589+
"Messages"
1590+
],
1591+
"summary": "Search incoming messages of a user",
1592+
"parameters": [
1593+
{
1594+
"type": "string",
1595+
"default": "+18005550199,+18005550100",
1596+
"description": "the owner's phone numbers",
1597+
"name": "owners",
1598+
"in": "query",
1599+
"required": true
1600+
},
1601+
{
1602+
"type": "string",
1603+
"description": "filter by message status",
1604+
"name": "statuses",
1605+
"in": "query"
1606+
},
1607+
{
1608+
"minimum": 0,
1609+
"type": "integer",
1610+
"description": "number of messages to skip",
1611+
"name": "skip",
1612+
"in": "query"
1613+
},
1614+
{
1615+
"type": "string",
1616+
"description": "filter messages containing query",
1617+
"name": "query",
1618+
"in": "query"
1619+
},
1620+
{
1621+
"type": "string",
1622+
"description": "field used to sort the messages",
1623+
"name": "sort_by",
1624+
"in": "query"
1625+
},
1626+
{
1627+
"type": "boolean",
1628+
"description": "sort messages in descending order",
1629+
"name": "sort_descending",
1630+
"in": "query"
1631+
},
1632+
{
1633+
"maximum": 200,
1634+
"minimum": 1,
1635+
"type": "integer",
1636+
"description": "number of messages to return",
1637+
"name": "limit",
1638+
"in": "query"
1639+
}
1640+
],
1641+
"responses": {
1642+
"200": {
1643+
"description": "OK",
1644+
"schema": {
1645+
"$ref": "#/definitions/responses.MessagesResponse"
1646+
}
1647+
},
1648+
"400": {
1649+
"description": "Bad Request",
1650+
"schema": {
1651+
"$ref": "#/definitions/responses.BadRequest"
1652+
}
1653+
},
1654+
"401": {
1655+
"description": "Unauthorized",
1656+
"schema": {
1657+
"$ref": "#/definitions/responses.Unauthorized"
1658+
}
1659+
},
1660+
"422": {
1661+
"description": "Unprocessable Entity",
1662+
"schema": {
1663+
"$ref": "#/definitions/responses.UnprocessableEntity"
1664+
}
1665+
},
1666+
"500": {
1667+
"description": "Internal Server Error",
1668+
"schema": {
1669+
"$ref": "#/definitions/responses.InternalServerError"
1670+
}
1671+
}
1672+
}
1673+
}
1674+
},
15741675
"/messages/outstanding": {
15751676
"get": {
15761677
"security": [

‎api/docs/swagger.json‎

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1568,6 +1568,107 @@
15681568
}
15691569
}
15701570
},
1571+
"/messages/incoming": {
1572+
"get": {
1573+
"security": [
1574+
{
1575+
"ApiKeyAuth": []
1576+
}
1577+
],
1578+
"description": "This returns the list of mobile-originated messages received by the user's phones. This route is scoped to messages:read and never returns other message types",
1579+
"consumes": [
1580+
"application/json"
1581+
],
1582+
"produces": [
1583+
"application/json"
1584+
],
1585+
"tags": [
1586+
"Messages"
1587+
],
1588+
"summary": "Search incoming messages of a user",
1589+
"parameters": [
1590+
{
1591+
"type": "string",
1592+
"default": "+18005550199,+18005550100",
1593+
"description": "the owner's phone numbers",
1594+
"name": "owners",
1595+
"in": "query",
1596+
"required": true
1597+
},
1598+
{
1599+
"type": "string",
1600+
"description": "filter by message status",
1601+
"name": "statuses",
1602+
"in": "query"
1603+
},
1604+
{
1605+
"minimum": 0,
1606+
"type": "integer",
1607+
"description": "number of messages to skip",
1608+
"name": "skip",
1609+
"in": "query"
1610+
},
1611+
{
1612+
"type": "string",
1613+
"description": "filter messages containing query",
1614+
"name": "query",
1615+
"in": "query"
1616+
},
1617+
{
1618+
"type": "string",
1619+
"description": "field used to sort the messages",
1620+
"name": "sort_by",
1621+
"in": "query"
1622+
},
1623+
{
1624+
"type": "boolean",
1625+
"description": "sort messages in descending order",
1626+
"name": "sort_descending",
1627+
"in": "query"
1628+
},
1629+
{
1630+
"maximum": 200,
1631+
"minimum": 1,
1632+
"type": "integer",
1633+
"description": "number of messages to return",
1634+
"name": "limit",
1635+
"in": "query"
1636+
}
1637+
],
1638+
"responses": {
1639+
"200": {
1640+
"description": "OK",
1641+
"schema": {
1642+
"$ref": "#/definitions/responses.MessagesResponse"
1643+
}
1644+
},
1645+
"400": {
1646+
"description": "Bad Request",
1647+
"schema": {
1648+
"$ref": "#/definitions/responses.BadRequest"
1649+
}
1650+
},
1651+
"401": {
1652+
"description": "Unauthorized",
1653+
"schema": {
1654+
"$ref": "#/definitions/responses.Unauthorized"
1655+
}
1656+
},
1657+
"422": {
1658+
"description": "Unprocessable Entity",
1659+
"schema": {
1660+
"$ref": "#/definitions/responses.UnprocessableEntity"
1661+
}
1662+
},
1663+
"500": {
1664+
"description": "Internal Server Error",
1665+
"schema": {
1666+
"$ref": "#/definitions/responses.InternalServerError"
1667+
}
1668+
}
1669+
}
1670+
}
1671+
},
15711672
"/messages/outstanding": {
15721673
"get": {
15731674
"security": [

‎api/docs/swagger.yaml‎

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2920,6 +2920,75 @@ paths:
29202920
summary: Register a missed call event on the mobile phone
29212921
tags:
29222922
- Messages
2923+
/messages/incoming:
2924+
get:
2925+
consumes:
2926+
- application/json
2927+
description: This returns the list of mobile-originated messages received by
2928+
the user's phones. This route is scoped to messages:read and never returns
2929+
other message types
2930+
parameters:
2931+
- default: +18005550199,+18005550100
2932+
description: the owner's phone numbers
2933+
in: query
2934+
name: owners
2935+
required: true
2936+
type: string
2937+
- description: filter by message status
2938+
in: query
2939+
name: statuses
2940+
type: string
2941+
- description: number of messages to skip
2942+
in: query
2943+
minimum: 0
2944+
name: skip
2945+
type: integer
2946+
- description: filter messages containing query
2947+
in: query
2948+
name: query
2949+
type: string
2950+
- description: field used to sort the messages
2951+
in: query
2952+
name: sort_by
2953+
type: string
2954+
- description: sort messages in descending order
2955+
in: query
2956+
name: sort_descending
2957+
type: boolean
2958+
- description: number of messages to return
2959+
in: query
2960+
maximum: 200
2961+
minimum: 1
2962+
name: limit
2963+
type: integer
2964+
produces:
2965+
- application/json
2966+
responses:
2967+
"200":
2968+
description: OK
2969+
schema:
2970+
$ref: '#/definitions/responses.MessagesResponse'
2971+
"400":
2972+
description: Bad Request
2973+
schema:
2974+
$ref: '#/definitions/responses.BadRequest'
2975+
"401":
2976+
description: Unauthorized
2977+
schema:
2978+
$ref: '#/definitions/responses.Unauthorized'
2979+
"422":
2980+
description: Unprocessable Entity
2981+
schema:
2982+
$ref: '#/definitions/responses.UnprocessableEntity'
2983+
"500":
2984+
description: Internal Server Error
2985+
schema:
2986+
$ref: '#/definitions/responses.InternalServerError'
2987+
security:
2988+
- ApiKeyAuth: []
2989+
summary: Search incoming messages of a user
2990+
tags:
2991+
- Messages
29232992
/messages/outstanding:
29242993
get:
29252994
consumes:

‎api/pkg/auth/mcp_token_verifier.go‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ var mcpDelegatedRoutes = []mcpDelegatedRoute{
3737
{method: http.MethodPost, segments: []string{"v1", "messages", "send"}, scope: "messages:send"},
3838
{method: http.MethodGet, segments: []string{"v1", "message-threads"}, scope: "messages:read"},
3939
{method: http.MethodGet, segments: []string{"v1", "messages"}, scope: "messages:read"},
40+
{method: http.MethodGet, segments: []string{"v1", "messages", "incoming"}, scope: "messages:read"},
4041
{method: http.MethodPost, segments: []string{"v1", "phone-api-keys"}, scope: "phone-api-keys:write"},
4142
{method: http.MethodDelete, segments: []string{"v1", "users", "*", "api-keys"}, scope: "user-api-key:rotate"},
4243
}

‎api/pkg/handlers/message_handler.go‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ func (h *MessageHandler) RegisterRoutes(router fiber.Router, middlewares ...fibe
5454
h.register(router, fiber.MethodPost, "/v1/messages/bulk-send", middlewares, h.BulkSend)
5555
h.register(router, fiber.MethodGet, "/v1/messages", middlewares, h.Index)
5656
h.register(router, fiber.MethodGet, "/v1/messages/search", middlewares, h.Search)
57+
h.register(router, fiber.MethodGet, "/v1/messages/incoming", middlewares, h.Incoming)
5758
h.register(router, fiber.MethodGet, "/v1/messages/:messageID", middlewares, h.Get)
5859
h.register(router, fiber.MethodDelete, "/v1/messages/:messageID", middlewares, h.Delete)
5960
}
@@ -548,3 +549,47 @@ func (h *MessageHandler) Search(c fiber.Ctx) error {
548549

549550
return h.responseOK(c, fmt.Sprintf("found %d %s", len(messages), h.pluralize("message", len(messages))), messages)
550551
}
552+
553+
// Incoming returns a filtered list of mobile-originated messages of a user
554+
// @Summary Search incoming messages of a user
555+
// @Description This returns the list of mobile-originated messages received by the user's phones. This route is scoped to messages:read and never returns other message types
556+
// @Security ApiKeyAuth
557+
// @Tags Messages
558+
// @Accept json
559+
// @Produce json
560+
// @Param owners query string true "the owner's phone numbers" default(+18005550199,+18005550100)
561+
// @Param statuses query string false "filter by message status"
562+
// @Param skip query int false "number of messages to skip" minimum(0)
563+
// @Param query query string false "filter messages containing query"
564+
// @Param sort_by query string false "field used to sort the messages"
565+
// @Param sort_descending query bool false "sort messages in descending order"
566+
// @Param limit query int false "number of messages to return" minimum(1) maximum(200)
567+
// @Success 200 {object} responses.MessagesResponse
568+
// @Failure 400 {object} responses.BadRequest
569+
// @Failure 401 {object} responses.Unauthorized
570+
// @Failure 422 {object} responses.UnprocessableEntity
571+
// @Failure 500 {object} responses.InternalServerError
572+
// @Router /messages/incoming [get]
573+
func (h *MessageHandler) Incoming(c fiber.Ctx) error {
574+
ctx, span, ctxLogger := h.tracer.StartFromFiberCtxWithLogger(c, h.logger)
575+
defer span.End()
576+
577+
var request requests.MessageIncoming
578+
if err := c.Bind().Query(&request); err != nil {
579+
ctxLogger.Warn(stacktrace.Propagatef(err, "cannot marshall params in [%s] into [%T]", c.OriginalURL(), request))
580+
return h.responseBadRequest(c, err)
581+
}
582+
583+
if errors := h.validator.ValidateMessageIncoming(ctx, request.Sanitize()); len(errors) != 0 {
584+
ctxLogger.Warn(stacktrace.NewErrorf("validation errors [%s], while fetching incoming messages [%+#v]", spew.Sdump(errors), request))
585+
return h.responseUnprocessableEntity(c, errors, "validation errors while fetching incoming messages")
586+
}
587+
588+
messages, err := h.service.SearchMessages(ctx, request.ToSearchParams(h.userIDFomContext(c)))
589+
if err != nil {
590+
ctxLogger.Error(stacktrace.Propagatef(err, "cannot fetch incoming messages with params [%+#v]", request))
591+
return h.responseInternalServerError(c)
592+
}
593+
594+
return h.responseOK(c, fmt.Sprintf("found %d %s", len(messages), h.pluralize("message", len(messages))), messages)
595+
}

0 commit comments

Comments
 (0)