Skip to content

Commit

Permalink
Merge pull request #38 from AgoraIO-Community/rtm-streamtoken
Browse files Browse the repository at this point in the history
Combined RTM + Stream Channel Tokens + `/getToken`
  • Loading branch information
maxxfrazer authored Jul 26, 2023
2 parents 9667a67 + 8625bfc commit b0d693d
Show file tree
Hide file tree
Showing 11 changed files with 637 additions and 65 deletions.
6 changes: 4 additions & 2 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ name: Go

on:
push:
branches: [ master, main ]
branches:
- main
pull_request:
branches: [ master, main ]
branches:
- main

jobs:
build:
Expand Down
90 changes: 90 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,96 @@ response:
{"message":"pong"}
```

### getToken ###

The `getToken` API endpoint allows you to generate tokens for different functionalities of the application. This section provides guidelines on how to use the `getToken` endpoint using HTTP POST requests.

### Endpoint URL

```
POST /getToken
```

### Request Body

The request body should contain a JSON payload with the required parameters for generating the tokens.

The following are the supported token types along with their required parameters:

1. **RTC Token:**

To generate an RTC token for video conferencing, include the following parameters in the request body:

```json
{
"tokenType": "rtc",
"channel": "your-channel-name",
"role": "publisher", // "publisher" or "subscriber"
"uid": "your-uid",
"expire": 3600 // optional: expiration time in seconds (default: 3600)
}
```

2. **RTM Token:**

To generate an RTM token for Real-Time Messaging, include the following parameters in the request body:

```json
{
"tokenType": "rtm",
"uid": "your-uid",
"expire": 3600 // optional: expiration time in seconds (default: 3600)
}
```

3. **Chat Token:**

To generate a chat token, include the following parameters in the request body:

```json
{
"tokenType": "chat",
"uid": "your-uid", // optional: for generating a user-specific chat token
"expire": 3600 // optional: expiration time in seconds (default: 3600)
}
```

### Response

Upon successful generation of the token, the API will respond with an HTTP status code of `200 OK`, and the response body will contain the token in a JSON key `"token"`.

If there is an error during token generation or if the request parameters are invalid, the API will respond with an appropriate HTTP status code and an error message in the response body.

### Sample Usage

Here's an example of how to use the `getToken` API endpoint with a POST request using cURL:

#### Request:

```bash
curl -X POST -H "Content-Type: application/json" -d '{
"tokenType": "rtc",
"channel": "my-video-channel",
"role": "publisher",
"uid": "user123",
"expire": 3600
}' "https://your-api-domain.com/getToken"
```

#### Reponse:

```json
{
"token": "007hbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhZG1pbiIsInN1YiI6InVzZXIxMjMiLCJpYXQiOjE2MzEwNTU4NzIsImV4cCI6MTYzMTA1OTQ3Mn0.3eJ-RGwIl2ANFbdv4SeHtWzGiv6PpC3i0UqXlHfsqEw"
}
```

---

## Deprecated Methods
The following methods are deprecated but still operational. While they continue to work for backward compatibility, it is advised to refrain from using them in new implementations due to potential future removal or replacement with more efficient alternatives.


### RTC Token ###
The `rtc` token endpoint requires a `tokenType` (uid || userAccount), `channelName`, and the user's `uid` (type varies based on `tokenType`).
`expiry(optional)` Pass an integer to represent the token lifetime in seconds.
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/AgoraIO-Community/agora-token-service
go 1.19

require (
github.com/AgoraIO-Community/go-tokenbuilder v1.2.0
github.com/AgoraIO-Community/go-tokenbuilder v1.3.0
github.com/gin-gonic/gin v1.9.1
github.com/joho/godotenv v1.3.0
)
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
github.com/AgoraIO-Community/go-tokenbuilder v1.2.0 h1:Ktlv7n8PhmSap3tgNxjNHO8hj/qydUIj4UOFF8tRxos=
github.com/AgoraIO-Community/go-tokenbuilder v1.2.0/go.mod h1:xqPdaiFG00M1hNN/CCYh8j+NTmkiJsQtqYdf4YAlncA=
github.com/AgoraIO-Community/go-tokenbuilder v1.3.0 h1:x/r/9UnmG9AnWGTH7TkEgbvZJKt2/phl50trw4WP4C4=
github.com/AgoraIO-Community/go-tokenbuilder v1.3.0/go.mod h1:xqPdaiFG00M1hNN/CCYh8j+NTmkiJsQtqYdf4YAlncA=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
Expand Down
63 changes: 47 additions & 16 deletions service/endpoints_test.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,50 @@
package service

import (
"bytes"
"log"
"net/http"
"net/http/httptest"
"testing"

"github.com/stretchr/testify/assert"
)

func TestGetTokenValidAndInvalid(t *testing.T) {

tests := []UrlCodePair{
{"/getToken", http.StatusOK, []byte(`{"tokenType": "rtc", "channel": "channel123", "role": "publisher", "uid": "user123", "expire": 3600}`)},
{"/getToken", http.StatusOK, []byte(`{"tokenType": "rtm", "uid": "user456", "expire": 1800}`)},
{"/getToken", http.StatusOK, []byte(`{"tokenType": "chat", "uid": "user789", "expire": 900}`)},
{"/getToken", http.StatusBadRequest, []byte(`{"channel": "channel456", "role": "subscriber", "expire": 1800}`)},
{"/getToken", http.StatusBadRequest, []byte(`{"tokenType": "invalid_type", "channel": "channel789", "role": "publisher", "uid": "user123", "expire": 3600}`)},
{"/getToken", http.StatusBadRequest, []byte(`{"tokenType": "rtc", "role": "publisher", "uid": "user123", "expire": 3600}`)},
{"/getToken", http.StatusBadRequest, []byte(`{"tokenType": "rtm", "expire": 1800}`)},
{"/getToken", http.StatusOK, []byte(`{"tokenType": "chat"}`)},
{"/getToken", http.StatusOK, []byte(`{"tokenType": "chat", "uid": "user123"}`)},
}
for _, httpTest := range tests {
testApi, err := http.NewRequest(http.MethodPost, httpTest.url, bytes.NewBuffer(httpTest.body))
log.Println(bytes.NewBuffer(httpTest.body))
if err != nil {
t.Fatal(err)
}
resp := httptest.NewRecorder()
// Call the endpoint
testService.Server.Handler.ServeHTTP(resp, testApi)
log.Println(httpTest.code)
log.Println(resp.Code)
assert.Equal(t, httpTest.code, resp.Code, resp.Body)
}
}

func TestRtcValidAndInvalid(t *testing.T) {

tests := []UrlCodePair{
{"/rtc/fsda/publisher/uid/0/?expiry=600", http.StatusOK},
{"/rtc/fsda/publisher/uid//?expiry=600", http.StatusOK},
{"/rtc/fsda/publisher/uid/test/?expiry=600", http.StatusBadRequest},
{"/rtc/fsda/publisher/uid/0/?expiry=failing", http.StatusBadRequest},
{"/rtc/fsda/publisher/uid/0/?expiry=600", http.StatusOK, nil},
{"/rtc/fsda/publisher/uid//?expiry=600", http.StatusOK, nil},
{"/rtc/fsda/publisher/uid/test/?expiry=600", http.StatusBadRequest, nil},
{"/rtc/fsda/publisher/uid/0/?expiry=failing", http.StatusBadRequest, nil},
}
for _, httpTest := range tests {
testApi, err := http.NewRequest(http.MethodGet, httpTest.url, nil)
Expand Down Expand Up @@ -58,17 +88,18 @@ func TestRtmValidAndInvalid(t *testing.T) {
type UrlCodePair struct {
url string
code int
body []byte
}

func TestChatValidAndInvalid(t *testing.T) {

tests := []UrlCodePair{
{"/chat/app/", http.StatusOK},
{"/chat/account/username/", http.StatusOK},
{"/chat/account/", http.StatusNotFound},
{"/chat/invalid/", http.StatusNotFound},
{"/chat/account/username/?expiry=600", http.StatusOK},
{"/chat/account/username/?expiry=fail", http.StatusBadRequest},
{"/chat/app/", http.StatusOK, nil},
{"/chat/account/username/", http.StatusOK, nil},
{"/chat/account/", http.StatusNotFound, nil},
{"/chat/invalid/", http.StatusNotFound, nil},
{"/chat/account/username/?expiry=600", http.StatusOK, nil},
{"/chat/account/username/?expiry=fail", http.StatusBadRequest, nil},
}
for _, httpTest := range tests {
testApi, err := http.NewRequest(http.MethodGet, httpTest.url, nil)
Expand All @@ -85,12 +116,12 @@ func TestChatValidAndInvalid(t *testing.T) {
func TestRteValidAndInvalid(t *testing.T) {

tests := []UrlCodePair{
{"/rte/channelName/publisher/uid/0/rtmid/?expiry=600", http.StatusOK},
{"/rte/channelName/publisher/uid/2345/?expiry=600", http.StatusOK},
{"/rte/channelName/subscriber/uid/0/rtmid/?expiry=600", http.StatusOK},
{"/rte/channelName/subscriber/uid/2345/?expiry=600", http.StatusOK},
{"/rte/channelName/publisher/uid/0/?expiry=600", http.StatusBadRequest},
{"/rte/channelName/publisher/uid/2345/?expiry=failing", http.StatusBadRequest},
{"/rte/channelName/publisher/uid/0/rtmid/?expiry=600", http.StatusOK, nil},
{"/rte/channelName/publisher/uid/2345/?expiry=600", http.StatusOK, nil},
{"/rte/channelName/subscriber/uid/0/rtmid/?expiry=600", http.StatusOK, nil},
{"/rte/channelName/subscriber/uid/2345/?expiry=600", http.StatusOK, nil},
{"/rte/channelName/publisher/uid/0/?expiry=600", http.StatusBadRequest, nil},
{"/rte/channelName/publisher/uid/2345/?expiry=failing", http.StatusBadRequest, nil},
}
for _, httpTest := range tests {
testApi, err := http.NewRequest(http.MethodGet, httpTest.url, nil)
Expand Down
20 changes: 10 additions & 10 deletions service/http_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
func (s *Service) getRtcToken(c *gin.Context) {
log.Println("Generating RTC token")
// get param values
channelName, tokenType, uidStr, _, role, expireTimestamp, err := s.parseRtcParams(c)
channelName, tokenType, uidStr, _, role, expire, err := s.parseRtcParams(c)

if err != nil {
c.Error(err)
Expand All @@ -23,7 +23,7 @@ func (s *Service) getRtcToken(c *gin.Context) {
return
}

rtcToken, tokenErr := s.generateRtcToken(channelName, uidStr, tokenType, role, expireTimestamp)
rtcToken, tokenErr := s.generateRtcToken(channelName, uidStr, tokenType, role, expire)

if tokenErr != nil {
log.Println(tokenErr) // token failed to generate
Expand All @@ -44,7 +44,7 @@ func (s *Service) getRtcToken(c *gin.Context) {
func (s *Service) getRtmToken(c *gin.Context) {
log.Println("Generating RTM token")
// get param values
uidStr, expireTimestamp, err := s.parseRtmParams(c)
uidStr, expire, err := s.parseRtmParams(c)

if err != nil {
c.Error(err)
Expand All @@ -55,7 +55,7 @@ func (s *Service) getRtmToken(c *gin.Context) {
return
}

rtmToken, tokenErr := rtmtokenbuilder2.BuildToken(s.appID, s.appCertificate, uidStr, expireTimestamp)
rtmToken, tokenErr := rtmtokenbuilder2.BuildToken(s.appID, s.appCertificate, uidStr, expire, "")

if tokenErr != nil {
c.Error(tokenErr)
Expand Down Expand Up @@ -105,23 +105,23 @@ func (s *Service) getChatToken(c *gin.Context) {
func (s *Service) getRtcRtmToken(c *gin.Context) {
log.Println("Generating RTC and RTM tokens")
// get rtc param values
channelName, tokenType, uidStr, rtmuid, role, expireTimestamp, rtcParamErr := s.parseRtcParams(c)
channelName, tokenType, uidStr, rtmuid, role, expire, rtcParamErr := s.parseRtcParams(c)

if rtcParamErr == nil && rtmuid == "" {
rtcParamErr = fmt.Errorf("failed to parse rtm user ID. Cannot be empty or \"0\"")
}
if rtcParamErr != nil {
c.Error(rtcParamErr)
c.AbortWithStatusJSON(400, gin.H{
"message": "Error Generating RTC token: " + rtcParamErr.Error(),
"message": "Error Generating RTC and RTM token: " + rtcParamErr.Error(),
"status": 400,
})
return
}
// generate the rtcToken
rtcToken, rtcTokenErr := s.generateRtcToken(channelName, uidStr, tokenType, role, expireTimestamp)
rtcToken, rtcTokenErr := s.generateRtcToken(channelName, uidStr, tokenType, role, expire)
// generate rtmToken
rtmToken, rtmTokenErr := rtmtokenbuilder2.BuildToken(s.appID, s.appCertificate, rtmuid, expireTimestamp)
rtmToken, rtmTokenErr := rtmtokenbuilder2.BuildToken(s.appID, s.appCertificate, rtmuid, expire, channelName)

if rtcTokenErr != nil {
c.Error(rtcTokenErr)
Expand All @@ -132,13 +132,13 @@ func (s *Service) getRtcRtmToken(c *gin.Context) {
})
} else if rtmTokenErr != nil {
c.Error(rtmTokenErr)
errMsg := "Error Generating RTC token - " + rtmTokenErr.Error()
errMsg := "Error Generating RTM token - " + rtmTokenErr.Error()
c.AbortWithStatusJSON(400, gin.H{
"status": 400,
"error": errMsg,
})
} else {
log.Println("RTC Token generated")
log.Println("RTC and RTM Tokens generated")
c.JSON(200, gin.H{
"rtcToken": rtcToken,
"rtmToken": rtmToken,
Expand Down
Loading

0 comments on commit b0d693d

Please sign in to comment.