├── go.mod ├── go.sum └── main.go /go.mod: -------------------------------------------------------------------------------- 1 | module agora-tutorial-server 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/AgoraIO-Community/go-tokenbuilder v1.0.0 7 | github.com/gin-gonic/gin v1.7.7 8 | github.com/joho/godotenv v1.4.0 9 | ) 10 | 11 | require ( 12 | github.com/gin-contrib/sse v0.1.0 // indirect 13 | github.com/go-playground/locales v0.13.0 // indirect 14 | github.com/go-playground/universal-translator v0.17.0 // indirect 15 | github.com/go-playground/validator/v10 v10.4.1 // indirect 16 | github.com/golang/protobuf v1.3.3 // indirect 17 | github.com/json-iterator/go v1.1.9 // indirect 18 | github.com/leodido/go-urn v1.2.0 // indirect 19 | github.com/mattn/go-isatty v0.0.12 // indirect 20 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect 21 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect 22 | github.com/ugorji/go/codec v1.1.7 // indirect 23 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect 24 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42 // indirect 25 | gopkg.in/yaml.v2 v2.2.8 // indirect 26 | ) 27 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/AgoraIO-Community/go-tokenbuilder v1.0.0 h1:R6L8a5u+p1qGpY5lNyQ19vRyDzIwhhqQnRlg9AAbqmA= 2 | github.com/AgoraIO-Community/go-tokenbuilder v1.0.0/go.mod h1:xqPdaiFG00M1hNN/CCYh8j+NTmkiJsQtqYdf4YAlncA= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 6 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 7 | github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= 8 | github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= 9 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 10 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= 11 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= 12 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= 13 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= 14 | github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= 15 | github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= 16 | github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= 17 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 18 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 19 | github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= 20 | github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 21 | github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= 22 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 23 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= 24 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= 25 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 26 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 27 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= 28 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 29 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= 30 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 31 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 32 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 33 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 34 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 35 | github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= 36 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= 37 | github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= 38 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 39 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 40 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= 41 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 42 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 43 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 44 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 45 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= 46 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 47 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 48 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 49 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 50 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 51 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 52 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 53 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 54 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "strconv" 8 | "time" 9 | 10 | "github.com/AgoraIO-Community/go-tokenbuilder/rtctokenbuilder" 11 | "github.com/AgoraIO-Community/go-tokenbuilder/rtmtokenbuilder" 12 | "github.com/gin-gonic/gin" 13 | "github.com/joho/godotenv" 14 | ) 15 | 16 | var appID string 17 | var appCertificate string 18 | 19 | func init() { 20 | os.Setenv("APP_ID", "41648987b4f940e38d137bd7119767f6") 21 | os.Setenv("APP_CERTIFICATE", "c7683668881e411abc9e3ce3c3d5fa45") 22 | // loads values from .env into the system 23 | if err := godotenv.Load(); err != nil { 24 | log.Print("No .env file found") 25 | } 26 | } 27 | 28 | func main() { 29 | appIDEnv, appIDExists := os.LookupEnv("APP_ID") 30 | appCertEnv, appCertExists := os.LookupEnv("APP_CERTIFICATE") 31 | 32 | if !appIDExists || !appCertExists { 33 | log.Fatal("FATAL ERROR: ENV not properly configured, check appID and appCertificate") 34 | } else { 35 | appID = appIDEnv 36 | appCertificate = appCertEnv 37 | } 38 | 39 | api := gin.Default() 40 | 41 | port := os.Getenv("PORT") 42 | if port == "" { 43 | port = "8080" 44 | } 45 | 46 | api.GET("/ping", func(c *gin.Context) { 47 | c.JSON(200, gin.H{ 48 | "message": "pong", 49 | }) 50 | }) 51 | 52 | api.Use(nocache()) 53 | api.GET("rtc/:channelName/:role/:tokentype/:uid/", getRtcToken) 54 | api.GET("rtm/:uid/", getRtmToken) 55 | api.GET("rte/:channelName/:role/:tokentype/:uid/", getBothTokens) 56 | api.Run(":" + port) // listen and serve on localhost:8080 57 | } 58 | 59 | func nocache() gin.HandlerFunc { 60 | return func(c *gin.Context) { 61 | // set headers 62 | c.Header("Cache-Control", "private, no-cache, no-store, must-revalidate") 63 | c.Header("Expires", "-1") 64 | c.Header("Pragma", "no-cache") 65 | c.Header("Access-Control-Allow-Origin", "*") 66 | } 67 | } 68 | 69 | func getRtcToken(c *gin.Context) { 70 | log.Printf("rtc token\n") 71 | // get param values 72 | channelName, tokentype, uidStr, role, expireTimestamp, err := parseRtcParams(c) 73 | 74 | if err != nil { 75 | c.Error(err) 76 | c.AbortWithStatusJSON(400, gin.H{ 77 | "message": "Error Generating RTC token: " + err.Error(), 78 | "status": 400, 79 | }) 80 | return 81 | } 82 | 83 | rtcToken, tokenErr := generateRtcToken(channelName, uidStr, tokentype, role, expireTimestamp) 84 | 85 | if tokenErr != nil { 86 | log.Println(tokenErr) // token failed to generate 87 | c.Error(tokenErr) 88 | errMsg := "Error Generating RTC token - " + tokenErr.Error() 89 | c.AbortWithStatusJSON(400, gin.H{ 90 | "status": 400, 91 | "error": errMsg, 92 | }) 93 | } else { 94 | log.Println("RTC Token generated") 95 | c.JSON(200, gin.H{ 96 | "rtcToken": rtcToken, 97 | }) 98 | } 99 | } 100 | 101 | func getRtmToken(c *gin.Context) { 102 | log.Printf("rtm token\n") 103 | // get param values 104 | uidStr, expireTimestamp, err := parseRtmParams(c) 105 | 106 | if err != nil { 107 | c.Error(err) 108 | c.AbortWithStatusJSON(400, gin.H{ 109 | "message": "Error Generating RTC token: " + err.Error(), 110 | "status": 400, 111 | }) 112 | return 113 | } 114 | 115 | rtmToken, tokenErr := rtmtokenbuilder.BuildToken(appID, appCertificate, uidStr, rtmtokenbuilder.RoleRtmUser, expireTimestamp) 116 | 117 | if tokenErr != nil { 118 | log.Println(tokenErr) // token failed to generate 119 | c.Error(tokenErr) 120 | errMsg := "Error Generating RTM token: " + tokenErr.Error() 121 | c.AbortWithStatusJSON(400, gin.H{ 122 | "error": errMsg, 123 | "status": 400, 124 | }) 125 | } else { 126 | log.Println("RTM Token generated") 127 | c.JSON(200, gin.H{ 128 | "rtmToken": rtmToken, 129 | }) 130 | } 131 | } 132 | 133 | func getBothTokens(c *gin.Context) { 134 | log.Printf("dual token\n") 135 | // get rtc param values 136 | channelName, tokentype, uidStr, role, expireTimestamp, rtcParamErr := parseRtcParams(c) 137 | 138 | if rtcParamErr != nil { 139 | c.Error(rtcParamErr) 140 | c.AbortWithStatusJSON(400, gin.H{ 141 | "message": "Error Generating RTC token: " + rtcParamErr.Error(), 142 | "status": 400, 143 | }) 144 | return 145 | } 146 | // generate the rtcToken 147 | rtcToken, rtcTokenErr := generateRtcToken(channelName, uidStr, tokentype, role, expireTimestamp) 148 | // generate rtmToken 149 | rtmToken, rtmTokenErr := rtmtokenbuilder.BuildToken(appID, appCertificate, uidStr, rtmtokenbuilder.RoleRtmUser, expireTimestamp) 150 | 151 | if rtcTokenErr != nil { 152 | log.Println(rtcTokenErr) // token failed to generate 153 | c.Error(rtcTokenErr) 154 | errMsg := "Error Generating RTC token - " + rtcTokenErr.Error() 155 | c.AbortWithStatusJSON(400, gin.H{ 156 | "status": 400, 157 | "error": errMsg, 158 | }) 159 | } else if rtmTokenErr != nil { 160 | log.Println(rtmTokenErr) // token failed to generate 161 | c.Error(rtmTokenErr) 162 | errMsg := "Error Generating RTC token - " + rtmTokenErr.Error() 163 | c.AbortWithStatusJSON(400, gin.H{ 164 | "status": 400, 165 | "error": errMsg, 166 | }) 167 | } else { 168 | log.Println("RTC Token generated") 169 | c.JSON(200, gin.H{ 170 | "rtcToken": rtcToken, 171 | "rtmToken": rtmToken, 172 | }) 173 | } 174 | 175 | } 176 | 177 | func parseRtcParams(c *gin.Context) (channelName, tokentype, uidStr string, role rtctokenbuilder.Role, expireTimestamp uint32, err error) { 178 | // get param values 179 | channelName = c.Param("channelName") 180 | roleStr := c.Param("role") 181 | tokentype = c.Param("tokentype") 182 | uidStr = c.Param("uid") 183 | expireTime := c.DefaultQuery("expiry", "3600") 184 | 185 | if roleStr == "publisher" { 186 | role = rtctokenbuilder.RolePublisher 187 | } else { 188 | role = rtctokenbuilder.RoleSubscriber 189 | } 190 | 191 | expireTime64, parseErr := strconv.ParseUint(expireTime, 10, 64) 192 | if parseErr != nil { 193 | // if string conversion fails return an error 194 | err = fmt.Errorf("failed to parse expireTime: %s, causing error: %s", expireTime, parseErr) 195 | } 196 | 197 | // set timestamps 198 | expireTimeInSeconds := uint32(expireTime64) 199 | currentTimestamp := uint32(time.Now().UTC().Unix()) 200 | expireTimestamp = currentTimestamp + expireTimeInSeconds 201 | 202 | return channelName, tokentype, uidStr, role, expireTimestamp, err 203 | } 204 | 205 | func parseRtmParams(c *gin.Context) (uidStr string, expireTimestamp uint32, err error) { 206 | // get param values 207 | uidStr = c.Param("uid") 208 | expireTime := c.DefaultQuery("expiry", "3600") 209 | 210 | expireTime64, parseErr := strconv.ParseUint(expireTime, 10, 64) 211 | if parseErr != nil { 212 | // if string conversion fails return an error 213 | err = fmt.Errorf("failed to parse expireTime: %s, causing error: %s", expireTime, parseErr) 214 | } 215 | 216 | // set timestamps 217 | expireTimeInSeconds := uint32(expireTime64) 218 | currentTimestamp := uint32(time.Now().UTC().Unix()) 219 | expireTimestamp = currentTimestamp + expireTimeInSeconds 220 | 221 | // check if string conversion fails 222 | return uidStr, expireTimestamp, err 223 | } 224 | 225 | func generateRtcToken(channelName, uidStr, tokentype string, role rtctokenbuilder.Role, expireTimestamp uint32) (rtcToken string, err error) { 226 | log.Printf(appID, appCertificate) 227 | if tokentype == "userAccount" { 228 | log.Printf("Building Token with userAccount: %s\n", uidStr) 229 | rtcToken, err = rtctokenbuilder.BuildTokenWithUserAccount(appID, appCertificate, channelName, uidStr, role, expireTimestamp) 230 | return rtcToken, err 231 | 232 | } else if tokentype == "uid" { 233 | uid64, parseErr := strconv.ParseUint(uidStr, 10, 64) 234 | // check if conversion fails 235 | if parseErr != nil { 236 | err = fmt.Errorf("failed to parse uidStr: %s, to uint causing error: %s", uidStr, parseErr) 237 | return "", err 238 | } 239 | 240 | uid := uint32(uid64) // convert uid from uint64 to uint 32 241 | log.Printf("Building Token with uid: %d\n", uid) 242 | rtcToken, err = rtctokenbuilder.BuildTokenWithUID(appID, appCertificate, channelName, uid, role, expireTimestamp) 243 | return rtcToken, err 244 | 245 | } else { 246 | err = fmt.Errorf("failed to generate RTC token for Unknown Tokentype: %s", tokentype) 247 | log.Println(err) 248 | return "", err 249 | } 250 | } 251 | --------------------------------------------------------------------------------