From 8c8755c88971d2fabd606778b703a8896ac11f38 Mon Sep 17 00:00:00 2001 From: Mupati Date: Sat, 7 Aug 2021 23:04:47 +0000 Subject: [PATCH] Initial commit --- .env.example | 2 + .gitignore | 16 ++++ README.md | 10 +++ go.mod | 9 ++ go.sum | 58 +++++++++++++ main.go | 228 +++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 323 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 README.md create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..a83b0cd --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +APP_ID= +APP_CERTIFICATE= \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..77e5879 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +.env +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..3974495 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# Agora Token Generator written in Go + +An RTM and RTC Token generator for Agora written in Go.
+This is directly from a tutorial on the [Agora Blog](https://www.agora.io/en/blog/how-to-build-a-token-server-using-golang) with a few modifications. + +## Project Setup +1. Clone the repository +2. Install the project dependencies +3. Copy the `.env.example` file into `.env` file and set your Agora Api Keys. +4. Start the server: `go run main.go` \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..a58347c --- /dev/null +++ b/go.mod @@ -0,0 +1,9 @@ +module agora-token-server + +go 1.13 + +require ( + github.com/AgoraIO-Community/go-tokenbuilder v1.0.0 + github.com/gin-gonic/gin v1.7.3 + github.com/joho/godotenv v1.3.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..92b4776 --- /dev/null +++ b/go.sum @@ -0,0 +1,58 @@ +github.com/AgoraIO-Community/go-tokenbuilder v1.0.0 h1:R6L8a5u+p1qGpY5lNyQ19vRyDzIwhhqQnRlg9AAbqmA= +github.com/AgoraIO-Community/go-tokenbuilder v1.0.0/go.mod h1:xqPdaiFG00M1hNN/CCYh8j+NTmkiJsQtqYdf4YAlncA= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.7.3 h1:aMBzLJ/GMEYmv1UWs2FFTcPISLrQH2mRgL9Glz8xows= +github.com/gin-gonic/gin v1.7.3/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= +github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go new file mode 100644 index 0000000..3264e48 --- /dev/null +++ b/main.go @@ -0,0 +1,228 @@ +package main + +import ( + "fmt" + "log" + "strconv" + "time" + + "github.com/AgoraIO-Community/go-tokenbuilder/rtctokenbuilder" + "github.com/AgoraIO-Community/go-tokenbuilder/rtmtokenbuilder" + "github.com/gin-gonic/gin" + "github.com/joho/godotenv" +) + +var appID, appCertificate string + +func main() { + + var envs map[string]string + envs, envsError := godotenv.Read(".env") + + if envsError != nil { + log.Fatal("Error loading .env file") + } + + appIDEnv := envs["APP_ID"] + appCertEnv := envs["APP_CERTIFICATE"] + + appID = appIDEnv + appCertificate = appCertEnv + + api := gin.Default() + + api.GET("rtc/:channelName/:role/:tokentype/:uid", getRtcToken) + api.GET("/rtm/:uid/", getRtmToken) + api.GET("/rte/:channelName/:role/:tokentype/:uid", getBothTokens) + + api.Run(":8080") +} + +func getRtcToken(c *gin.Context) { + log.Printf("rtc token\n") + + // get param values + channelName, tokentype, uidStr, role, expireTimestamp, err := parseRtcParams(c) + + if err != nil { + c.Error(err) + c.AbortWithStatusJSON(400, gin.H{ + "message": "Error Generating RTC token: " + err.Error(), + "status": 400, + }) + return + } + + rtcToken, tokenErr := generateRtcToken(channelName, uidStr, tokentype, role, expireTimestamp) + + if tokenErr != nil { + log.Println(tokenErr) // token failed to generate + c.Error(tokenErr) + + errMsg := "Error Generating RTC token - " + tokenErr.Error() + c.AbortWithStatusJSON(400, gin.H{ + "status": 400, + "error": errMsg, + }) + } else { + log.Println("RTC Token generated") + c.JSON(200, gin.H{ + "rtcToken": rtcToken, + }) + } +} + +func getRtmToken(c *gin.Context) { + + log.Printf("rtm token\n") + + // get param values + uidStr, expireTimestamp, err := parseRtmParams(c) + + if err != nil { + c.Error(err) + c.AbortWithStatusJSON(400, gin.H{ + "message": "Error Generating RTC token: " + err.Error(), + "status": 400, + }) + return + } + + rtmToken, tokenErr := rtmtokenbuilder.BuildToken(appID, appCertificate, uidStr, rtmtokenbuilder.RoleRtmUser, expireTimestamp) + + if tokenErr != nil { + log.Println(err) // token failed to generate + c.Error(err) + errMsg := "Error Generating RTM token: " + tokenErr.Error() + c.AbortWithStatusJSON(400, gin.H{ + "error": errMsg, + "status": 400, + }) + } else { + log.Println("RTM Token generated") + c.JSON(200, gin.H{ + "rtmToken": rtmToken, + }) + } + +} + +func getBothTokens(c *gin.Context) { + log.Printf("dual token\n") + // get rtc param values + channelName, tokentype, uidStr, role, expireTimestamp, rtcParamErr := parseRtcParams(c) + + if rtcParamErr != nil { + c.Error(rtcParamErr) + c.AbortWithStatusJSON(400, gin.H{ + "message": "Error Generating RTC token: " + rtcParamErr.Error(), + "status": 400, + }) + return + } + // generate the rtcToken + rtcToken, rtcTokenErr := generateRtcToken(channelName, uidStr, tokentype, role, expireTimestamp) + // generate rtmToken + rtmToken, rtmTokenErr := rtmtokenbuilder.BuildToken(appID, appCertificate, uidStr, rtmtokenbuilder.RoleRtmUser, expireTimestamp) + + if rtcTokenErr != nil { + log.Println(rtcTokenErr) // token failed to generate + c.Error(rtcTokenErr) + errMsg := "Error Generating RTC token - " + rtcTokenErr.Error() + c.AbortWithStatusJSON(400, gin.H{ + "status": 400, + "error": errMsg, + }) + } else if rtmTokenErr != nil { + log.Println(rtmTokenErr) // token failed to generate + c.Error(rtmTokenErr) + errMsg := "Error Generating RTC token - " + rtmTokenErr.Error() + c.AbortWithStatusJSON(400, gin.H{ + "status": 400, + "error": errMsg, + }) + } else { + log.Println("RTC Token generated") + c.JSON(200, gin.H{ + "rtcToken": rtcToken, + "rtmToken": rtmToken, + }) + } +} + +func parseRtcParams(c *gin.Context) (channelName, tokentype, uidStr string, role rtctokenbuilder.Role, expireTimestamp uint32, err error) { + // get param values + channelName = c.Param("channelName") + roleStr := c.Param("role") + tokentype = c.Param("tokentype") + uidStr = c.Param("uid") + expireTime := c.DefaultQuery("expiry", "3600") + + if roleStr == "publisher" { + role = rtctokenbuilder.RolePublisher + } else { + role = rtctokenbuilder.RoleSubscriber + } + + expireTime64, parseError := strconv.ParseUint(expireTime, 10, 64) + + if parseError != nil { + // if string conversion fails return an error + err = fmt.Errorf("failed to parse expireTime: %s, causing error: %s", expireTime, parseError) + } + + // set timestamps + expireTimeInSeconds := uint32(expireTime64) + currentTimestamp := uint32(time.Now().UTC().Unix()) + expireTimestamp = currentTimestamp + expireTimeInSeconds + + return channelName, tokentype, uidStr, role, expireTimestamp, err +} + +func parseRtmParams(c *gin.Context) (uidStr string, expireTimestamp uint32, err error) { + // get param values + uidStr = c.Param("uid") + expireTime := c.DefaultQuery("expiry", "3600") + + expireTime64, parseErr := strconv.ParseUint(expireTime, 10, 64) + if parseErr != nil { + // if string conversion fails return an error + err = fmt.Errorf("failed to parse expireTime: %s, causing error: %s", expireTime, parseErr) + } + + // set timestamps + expireTimeInSeconds := uint32(expireTime64) + currentTimestamp := uint32(time.Now().UTC().Unix()) + expireTimestamp = currentTimestamp + expireTimeInSeconds + + // check if string conversion fails + return uidStr, expireTimestamp, err +} + +func generateRtcToken(channelName, uidStr, tokentype string, role rtctokenbuilder.Role, expireTimestamp uint32) (rtcToken string, err error) { + if tokentype == "userAccount" { + log.Printf("Building Token with userAccount: %s\n", uidStr) + rtcToken, err = rtctokenbuilder.BuildTokenWithUserAccount(appID, appCertificate, channelName, uidStr, role, expireTimestamp) + + return rtcToken, err + + } else if tokentype == "uid" { + + uid64, parseErr := strconv.ParseUint(uidStr, 10, 64) + + // check if conversion fails + if parseErr != nil { + err = fmt.Errorf("failed to parse uidStr: %s, to uint causing error: %s", uidStr, parseErr) + return "", err + } + + uid := uint32(uid64) // convert uid from uint64 to uint 32 + log.Printf("Building Token with uid: %d\n", uid) + rtcToken, err = rtctokenbuilder.BuildTokenWithUID(appID, appCertificate, channelName, uid, role, expireTimestamp) + return rtcToken, err + } else { + err = fmt.Errorf("failed to generate RTC token for Unknown Tokentype: %s", tokentype) + log.Println(err) + return "", err + } +}