Agora start Cloud Recording returns 400 with no error message - agora.io

I'm trying to set up Cloud Recording for Agora. I'm able to call the acquire method and obtain a Resource ID, but when I try to start recording I get a 400 response that has no error code or reason message. If I change any part of the request body I will get an error code of 2 with a reason message, so I think my request is formatted properly. Also, since the first request to acquire the resource ID is successful, I don't think there should be an authentication issue. The code to generate the RTC Token is being used elsewhere to create the channel and users are able to join channels in the app.
requestBody := fmt.Sprintf(`
{
"cname": "%s",
"uid": "%d",
"clientRequest": {
"token": "%s",
"recordingConfig": {
"maxIdleTime": 30,
"streamTypes": 0,
"channelType": 1,
"transcodingConfig": {
"height": 720,
"width": 1280,
"bitrate": 2260,
"fps": 15,
"mixedVideoLayout": 1,
"backgroundColor": "#000000"
}
},
"storageConfig": {
"vendor": %d,
"region": %d,
"bucket": "%s",
"accessKey": "%s",
"secretKey": "%s",
"fileNamePrefix": ["tracks"]
}
}
}
`, channelID, uid, rtcToken, 1 /*AWS*/, region, os.Getenv("AGORA_BUCKET_NAME"),
os.Getenv("AGORA_BUCKET_ACCESS_KEY"), os.Getenv("AGORA_BUCKET_ACCESS_SECRET"),
channelID)
reqUrl := "https://api.agora.io/v1/apps/" + os.Getenv("AGORA_APP_ID") + "/cloud_recording/resourceid/" + rid + "/mode/mix/start"
req, err := http.NewRequest("POST", reqUrl,
bytes.NewBuffer([]byte(requestBody)))
if err != nil {
return response, err
}
req.Header.Set("Content-Type", "application/json")
req.SetBasicAuth(os.Getenv("AGORA_CUSTOMER_ID"), os.Getenv("AGORA_CUSTOMER_CERTIFICATE"))
resp, err := httpClient.Do(req)
if err != nil {
return response, err
}
if resp.StatusCode != http.StatusOK {
// this is a 400 with no error code or reason
logger.Log("statuscode", resp.StatusCode)
}

The reason you are able to get a resourceID is because your appID has Cloud Recording enable and a 400 response from start means your request is properly formatted and successfully making the call to start the Agora Cloud Recorder instance but something is causing the Agora Cloud Recorder to fail to start.
Have you tried using the Agora Postman collection to test? I would recommend using it within Postman because it offers a working config that requires you to set your connection credentials into the variables and it should work if your bucket is properly configured.
I wrote this quick-start guide on how to set up Cloud Recording and use the Postman collection to test.

Related

Telegram Bot Webhook - Connection Refused (Golang)

I have configured a Telegram bot to use Webhooks according to the following configuration:
OS: Ubuntu
Server: Apache2
Virtual Host:
Sucessfully linked to domain
Listening on port 8843
Ports 443, 8843 all open with ufw allow
Webhook:
Set to "domain.app:8843/" bot.Token
HandleFunc set to "/" + bot.Token
certFile and keyFile are used succesfully with ListenAndServeTLS
Still, however, my bot just won't work, spend literally the entire last 18 hours trying to fix it. It is 3 at night now, and I just am at my wits end.
When I start my binary it prints the following to stdout:
2023/01/07 01:50:15 Authorized on account ExampleAccount
2023/01/07 01:50:15 setWebhook resp:
{"ok":true,"result":true,"description":"Webhook is already set"}
2023/01/07 01:50:15 getWebhookInfo resp:
{"ok":true,"result":{"url":"https://domain.app:8443/MY_BOT_TOKEN","has_custom_certificate":false,"pending_update_count":0,"last_error_date":1673052633,"last_error_message":"Connection
refused","max_connections":40,"ip_address":"x.x.x.x"}}
2023/01/07 01:50:15 Telegram callback failed: Connection refused
If somebody has any idea please help me!
2023/01/07 02:07:57 getWebhookInfo resp:
{"ok":true,"result":{"url":"https://domain.app:8443/MY_BOT_TOKEN","has_custom_certificate":false,"pending_update_count":3,"last_error_date":1673052633,"last_error_message":"Connection
refused","max_connections":40,"ip_address":"167.99.34.89"}}
If I issue start commands to the bot, the pending_update_count actually gets incremented as well, so this is a really strange issue.
My code:
func main() {
// Create a new bot
bot, err := tgbotapi.NewBotAPI("PLACEHOLDER")
if err != nil {
log.Panic(err)
}
bot.Debug = true
log.Printf("Authorized on account %s", bot.Self.UserName)
// Set the webhook
_, err = bot.SetWebhook(tgbotapi.NewWebhook("https://domain.app:8443/" + bot.Token))
if err != nil {
log.Panic(err)
}
info, err := bot.GetWebhookInfo()
if err != nil {
log.Fatal(err)
}
if info.LastErrorDate != 0 {
log.Printf("Telegram callback failed: %s", info.LastErrorMessage)
}
// Start the webhook server
http.HandleFunc("/"+bot.Token, func(w http.ResponseWriter, r *http.Request) {
update := tgbotapi.Update{}
err := json.NewDecoder(r.Body).Decode(&update)
if err != nil {
log.Println(err)
return
}
// Check if the update is a callback query (button press)
if update.CallbackQuery != nil {
callbackQuery := update.CallbackQuery
data := callbackQuery.Data
// Create a new message
msg := tgbotapi.NewMessage(callbackQuery.Message.Chat.ID, "")
// Set the text and keyboard based on the button pressed
switch data {
case "get_information":
msg.Text = "Here is some information about which formats are supported:"
msg.ReplyMarkup = tgbotapi.NewRemoveKeyboard(true)
case "start_file_conversion":
msg.Text = "Great! Click the link to download your file: https://example.com/ks283dj"
msg.ReplyMarkup = tgbotapi.NewRemoveKeyboard(true)
}
// Send the message
bot.Send(msg)
}
})
log.Printf("About to listen on 0.0.0.0:8443:8443")
errServe := http.ListenAndServeTLS("0.0.0.0:8443", "fullchain.pem", "privkey.pem", nil)
log.Fatal(errServe)
}
The Webhook setup is wrong here:
Instead of:
_, err = bot.SetWebhook(tgbotapi.NewWebhook("https://domain.app:8443/" + bot.Token))
http.HandleFunc("/"+bot.Token, func(w http.ResponseWriter, r *http.Request)
Use:
_, err = bot.SetWebhook(tgbotapi.NewWebhook("https://domain.app:8443/bot" + bot.Token))
http.HandleFunc("/bot"+bot.Token, func(w http.ResponseWriter, r *http.Request)
As described here: https://core.telegram.org/bots/api:
All queries to the Telegram Bot API must be served over HTTPS and need
to be presented in this form:
https://api.telegram.org/bot/METHOD_NAME.

Attempting to subscribe to a Shopify Webhook w/AWS EventBridge produces error: "Address is an AWS ARN and includes api_client_id 'x' instead of 'y'"

I'm running this request through Postman. Some posts to the Shopify developer forum (e.g., this one) express without clear explanation that the request should be made within the Shopify app that would be subscribing to the Webhooks, but Postman seems to work, too.
In Postman . . .
Here's the endpoint:
https://{{shopifyDevelopmentStoreName}}.myshopify.com/admin/api/2022-07/graphql.json
Here's the GraphQL body:
mutation createWebhookSubscription($topic: WebhookSubscriptionTopic!, $webhookSubscription: EventBridgeWebhookSubscriptionInput!) {
eventBridgeWebhookSubscriptionCreate(
topic: $topic,
webhookSubscription: $webhookSubscription
) {
webhookSubscription {
id
}
userErrors {
message
}
}
}
Here's the payload being sent (notice the "client_id_x" value within the arn property):
{
"topic": "PRODUCTS_CREATE",
"webhookSubscription": {
"arn": "arn:aws:events:us-east-1::event-source/aws.partner/shopify.com/client_id_x/LovecraftEventBridgeSource",
"format": "JSON",
"includeFields": "id"
}
}
Here's the response I receive:
{
"data": {
"eventBridgeWebhookSubscriptionCreate": {
"webhookSubscription": null,
"userErrors": [
{
"message": "Address is invalid"
},
{
"message": "Address is an AWS ARN and includes api_client_id 'client_id_x' instead of 'client_id_y'"
}
]
}
},
"extensions": {
"cost": {
"requestedQueryCost": 10,
"actualQueryCost": 10,
"throttleStatus": {
"maximumAvailable": 1000.0,
"currentlyAvailable": 990,
"restoreRate": 50.0
}
}
}
}
What's entirely unclear is why Shopify is insisting upon validity of "client_id_y" when, in AWS, the value being displayed is undeniably 'client_id_x'. Extremely confusing. I don't even see what difference using the Shopify app would make except that it produces a client_id value that works counter to one's expectations and intuitions.
Does anyone know why the heck Shopify isn't just using the client_id value of the event bus created earlier in Amazon EventBridge?
Same happend to me and I was lucky to find a solution.
The error message is just missleading.
I replaced the API Access Token for the Shopify Rest API Request (X-Shopify-Access-Token)
with the one from the Shopify App holding the aws credentials.
admin/settings/apps/development -> app -> API credentials -> Admin API access token. (can only be seen after creation)
Then I could subscribe webhooks to the app via the Rest Interface.

Agora cloud recording not starting properly

I'm facing some issues in agora cloud recording with amazon S3. Not able to connect s3 properly. Getting 404 error when running postman.
{
"cname": "Stream-On",
"uid": "527841",
"clientRequest": {
"token": "temp_token",
"recordingConfig": {
"maxIdleTime": 120,
"streamTypes": 2,
"audioProfile": 1,
"channelType": 1,
"videoStreamType": 0,
"transcodingConfig": {
"width": 360,
"height": 640,
"fps": 30,
"bitrate": 600,
"mixedVideoLayout": 1,
"maxResolutionUid": "1"
}
},
"storageConfig": {
"vendor": 1,
"region": 8,
"bucket": "live-stream",
"accessKey": "Axxxxxxxxxxxxxxxx",
"secretKey": "Axxxxxxxxxxxxxxxxxxx"
}
}
}
When trying to start my cloud recording I am receiving the resourceId and sid but and I try to stop it shows Failed to find worker and my amazon s3 bucket is also still empty
{
"resourceId": "temp_resource_id",
"code": 404,
"serverResponse": {
"command": "StopCloudRecorder",
"payload": {
"message": "Failed to find worker."
},
"subscribeModeBitmask": 1,
"vid": "471258"
}
}
Can anyone please help to fix this issue?
Generally a 404 response from stop means your original request for start was properly formatted and successfully making the call to start the Agora Cloud Recorder instance but something caused the Agora Cloud Recorder worker to fail to start, so when you call stop there is nothing to stop because nothing actually started.
I wrote this quick-start guide on how to set up Cloud Recording and use the Agora Postman collection to test that your storage bucket is properly configured. I would recommend using it within Postman because it offers a working config that requires you to set your connection credentials into the variables and it should work if your bucket is properly configured.

googleapi: Error 400: Bad Request, failedPrecondition

I'm trying to get message information from the Google API using a service account and a JWT.
I've already enabled Domain wide delegation. I should probably mention that I'm not a GSuite admin, just a user who is trying to get messages from my own Inbox using a Go script.
func main() {
data, err := ioutil.ReadFile("JWT_Credentials.json")
conf, err := google.JWTConfigFromJSON(data, gmail.GmailReadonlyScope)
client := conf.Client(oauth2.NoContext)
// Create a new gmail service using the client
gmailService, err := gmail.New(client)
fullMessage, err := gmailService.Users.Messages.Get("me", message.Id).Format("metadata").Do()
if err != nil {
log.Printf("Error: %v", err)
}
}
and my JWT credentials are as follows:
{
"type": "service_account",
"project_id": "PROJECT_ID",
"private_key_id": "PRIVATE_KEY_ID",
"private_key": "-----BEGIN PRIVATE KEY-----\PRIVATE_KEY",
"client_email": "SERVICE#PROJECT_ID.iam.gserviceaccount.com",
"client_id": "CLIENT_ID",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url":
"https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "CERT_URL"
}
I get the following error when the code runs:
googleapi: Error 400: Bad Request, failedPrecondition
If anybody has the solution to this problem or has encountered it in the past, please help me out!

Keycloak adaptor for golang application

I am going to secure my golang application using keycloak, but keycloak itself does not support go language.
There are some go adaptor as an open project in github that has implemented openId connect protocol as a provider service, but they do not provide an example or documentation on how to integrate libraries with an application.
How can i interact with keycloak using golang?
As you have pointed out, there is no official keycloak adapter for golang.
But it is pretty straight forward to implement it. Here is a little walk through.
Keycloak server
For this example, I will use the official keycloak docker image to start the server.
The version used is 4.1.0.Final. I think this will work with older KeyCloak versions too though.
docker run -d -p 8080:8080 -e KEYCLOAK_USER=keycloak -e KEYCLOAK_PASSWORD=k --name keycloak jboss/keycloak:4.1.0.Final
After the server is up and running, you can open localhost:8080/auth in your browser, navigate to the administration console and login with username keycloak and k as the corresponding password.
I will not go through the complete process of creating a realm/clients/users. You can look this up under
https://www.keycloak.org/docs/latest/server_admin/index.html#admin-console
Here is just an outline for what I did to reproduce this example:
create a realm named demo
turn off the requirement of ssl for this realm (realmsettings -> login -> require ssl)
create a client named demo-client (change the "Access Type" to confidential)
create a user named demo with password demo (users -> add user). Make sure to activate and impersonate this user.
configure the demo-client to be confidential and use http://localhost:8181/demo/callback as a valid redirect URI.
The resulting keycloak.json (obtained from the installation tab) looks like this:
{
"realm": "demo",
"auth-server-url": "http://localhost:8080/auth",
"ssl-required": "none",
"resource": "demo-client",
"credentials": {
"secret": "cbfd6e04-a51c-4982-a25b-7aaba4f30c81"
},
"confidential-port": 0
}
Beware that your secret will be different though.
The Go Server
Let's go over to the go server. I use the github.com/coreos/go-oidc package for the heavy lifting:
package main
import (
"context"
"encoding/json"
"log"
"net/http"
"strings"
oidc "github.com/coreos/go-oidc"
"golang.org/x/oauth2"
)
func main() {
configURL := "http://localhost:8080/auth/realms/demo"
ctx := context.Background()
provider, err := oidc.NewProvider(ctx, configURL)
if err != nil {
panic(err)
}
clientID := "demo-client"
clientSecret := "cbfd6e04-a51c-4982-a25b-7aaba4f30c81"
redirectURL := "http://localhost:8181/demo/callback"
// Configure an OpenID Connect aware OAuth2 client.
oauth2Config := oauth2.Config{
ClientID: clientID,
ClientSecret: clientSecret,
RedirectURL: redirectURL,
// Discovery returns the OAuth2 endpoints.
Endpoint: provider.Endpoint(),
// "openid" is a required scope for OpenID Connect flows.
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
}
state := "somestate"
oidcConfig := &oidc.Config{
ClientID: clientID,
}
verifier := provider.Verifier(oidcConfig)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
rawAccessToken := r.Header.Get("Authorization")
if rawAccessToken == "" {
http.Redirect(w, r, oauth2Config.AuthCodeURL(state), http.StatusFound)
return
}
parts := strings.Split(rawAccessToken, " ")
if len(parts) != 2 {
w.WriteHeader(400)
return
}
_, err := verifier.Verify(ctx, parts[1])
if err != nil {
http.Redirect(w, r, oauth2Config.AuthCodeURL(state), http.StatusFound)
return
}
w.Write([]byte("hello world"))
})
http.HandleFunc("/demo/callback", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("state") != state {
http.Error(w, "state did not match", http.StatusBadRequest)
return
}
oauth2Token, err := oauth2Config.Exchange(ctx, r.URL.Query().Get("code"))
if err != nil {
http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
return
}
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
if !ok {
http.Error(w, "No id_token field in oauth2 token.", http.StatusInternalServerError)
return
}
idToken, err := verifier.Verify(ctx, rawIDToken)
if err != nil {
http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
return
}
resp := struct {
OAuth2Token *oauth2.Token
IDTokenClaims *json.RawMessage // ID Token payload is just JSON.
}{oauth2Token, new(json.RawMessage)}
if err := idToken.Claims(&resp.IDTokenClaims); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data, err := json.MarshalIndent(resp, "", " ")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Write(data)
})
log.Fatal(http.ListenAndServe("localhost:8181", nil))
}
This program starts a regular http server with two endpoints. The first one ("/") is your regular endpoint that handles
application logic. In this case, it only returns "hello world" to your client.
The second endpoint ("/demo/callback") is used as a callback for keycloak. This endpoint needs to be registered on your
keycloak server. Keycloak will issue a redirect to this callback URL upon successful user authentication. The redirect contains some additional query parameters. These parameters contain a code that can be used to obtain access/id tokens.
Verify your setup
In order to test this setup you can open a webbrowser and navitage to http://localhost:8181.
The request should reach your go server, which tries to authenticate you. Since you did not send a token, the go server
will redirecty you to keycloak to authenticate.
You should see the login screen of keycloak. Login with the demo user you have created for this realm (demo/demo).
If you have configured your keycloak correctly, it will authenticate you and redirect you to your go server callback.
The end result should be a json like this
{
"OAuth2Token": {
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJsc1hHR2VxSmx3UUZweWVYR0x6b2plZXBYSEhXUngtTHVJTVVLdDBmNmlnIn0.eyJqdGkiOiI5ZjAxNjM2OC1lYmEwLTRiZjMtYTU5Ni1kOGU1MzdmNTNlZGYiLCJleHAiOjE1MzIxNzM2NTIsIm5iZiI6MCwiaWF0IjoxNTMyMTczMzUyLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvZGVtbyIsImF1ZCI6ImRlbW8tY2xpZW50Iiwic3ViIjoiMzgzMzhjOGItYWQ3Zi00NjlmLTgzOTgtMTc5ODk1ODFiYTEyIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiZGVtby1jbGllbnQiLCJhdXRoX3RpbWUiOjE1MzIxNzMzNTIsInNlc3Npb25fc3RhdGUiOiJjZTg2NWFkZC02N2I4LTQ5MDUtOGYwMy05YzE2MDNjMWJhMGQiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIGVtYWlsIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsInByZWZlcnJlZF91c2VybmFtZSI6ImRlbW8iLCJlbWFpbCI6ImRlbW9AZGVtby5jb20ifQ.KERz8rBddxM9Qho3kgigX-fClWqbKY-3JcWT3JOQDoLa-prkorfa40BWlyf9ULVgjzT2d8FLJpqQIQYvucKU7Q7vFBVIjTGucUZaE7b6JGMea5H34A1i-MNm7L2CzDJ2GnBONhNwLKoftTSl0prbzwkzcVrps-JAZ6L2gssSa5hBBGJYBKAUfm1OIb57Jq0vzro3vLghZ4Ay7iNunwfcHUrxiFJfUjaU6PQwzrA5pnItOPuavJFUgso7-3JLtn3X9GQuyyZKrkDo6-gzU0JZmkQQzAXXgt43NxooryImuacwSB5xbIKY6qFkedldoOPehld1-oLv0Yy_FIwEad3uLw",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJsc1hHR2VxSmx3UUZweWVYR0x6b2plZXBYSEhXUngtTHVJTVVLdDBmNmlnIn0.eyJqdGkiOiI0MjdmMTlhYy1jMTkzLTQ2YmQtYWFhNi0wY2Q1OTI5NmEwMGQiLCJleHAiOjE1MzIxNzUxNTIsIm5iZiI6MCwiaWF0IjoxNTMyMTczMzUyLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvZGVtbyIsImF1ZCI6ImRlbW8tY2xpZW50Iiwic3ViIjoiMzgzMzhjOGItYWQ3Zi00NjlmLTgzOTgtMTc5ODk1ODFiYTEyIiwidHlwIjoiUmVmcmVzaCIsImF6cCI6ImRlbW8tY2xpZW50IiwiYXV0aF90aW1lIjowLCJzZXNzaW9uX3N0YXRlIjoiY2U4NjVhZGQtNjdiOC00OTA1LThmMDMtOWMxNjAzYzFiYTBkIiwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIGVtYWlsIn0.FvvDW6ZSH8mlRR2zgaN1zesX14SmkCs9RrIVU4Jn1-SHVdKEA6YKur0-RUAFTObQDMLVhFFJ05AjGVGWpBrgVDcAwW2pI9saM-OHlyTJ3VfFoylgfzakVOIpbIDnHO12UaJrkOI9NWPAJdbBOzBHfsDhKbxhjg4ZX8SwlKr42rV4WWuSRcNu4_YDVO19SiXSCKXVldZ1_2S-qPvViq7VZfaoRLHuYyDvma_ByMsmib9JUkevJ8dxsYxVQ5FWaAfFanh1a1f8HxNRI-Cl180oPn1_Tqq_SYwxzBCw7Q_ENkMirwRS1a4cX9yMVEDW2uvKz2D-OiNAUK8d_ONuPEkTGQ",
"expiry": "2018-07-21T13:47:28.986686385+02:00"
},
"IDTokenClaims": {
"jti": "f4d56526-37d9-4d32-b99d-81090e92d3a7",
"exp": 1532173652,
"nbf": 0,
"iat": 1532173352,
"iss": "http://localhost:8080/auth/realms/demo",
"aud": "demo-client",
"sub": "38338c8b-ad7f-469f-8398-17989581ba12",
"typ": "ID",
"azp": "demo-client",
"auth_time": 1532173352,
"session_state": "ce865add-67b8-4905-8f03-9c1603c1ba0d",
"acr": "1",
"email_verified": true,
"preferred_username": "demo",
"email": "demo#demo.com"
}
}
You can copy your access token and use curl to verify if the server is able to accept your tokens:
# use your complete access token here
export TOKEN="eyJhbG..."
curl -H "Authorization: Bearer $TOKEN" localhost:8181
# output hello world
You can try it again after the token has expired - or temper with the token. In case you do it, you should get a redirect to
your keycloak server again.
There is also the gocloak library which provides lot's of functionality. The lib is in active development and allready in use in real world projects. So possible bugs & feature requests are being handled.
It provides administration features like "CreateUser","CreateGroup" etc. and also provides functions for Login, Token validation, etc.
For example creating a user is as easy as:
client := gocloak.NewClient("https://mycool.keycloak.instance")
token, err := client.LoginAdmin("user", "password", "realmName")
if err != nil {
panic("Something wrong with the credentials or url")
}
user := gocloak.User{
FirstName: "Bob",
LastName: "Uncle",
EMail: "something#really.wrong",
Enabled: true,
Username: "CoolGuy",
}
client.CreateUser(token.AccessToken, "realm", user)
if err != nil {
panic("Oh no!, failed to create user :(")
}
It does also supports Introspecting a Requesting Party Token
client := gocloak.NewClient(hostname)
token, err := client.LoginClient(clientid, clientSecret, realm)
if err != nil {
panic("Login failed:"+ err.Error())
}
rptResult, err := client.RetrospectToken(token.AccessToken, clientid, clientSecret, realm)
if err != nil {
panic("Inspection failed:"+ err.Error())
}
if !rptResult.Active {
panic("Token is not active")
}
permissions := rptResult.Permissions
//Do something with the permissions ;)
Also to handle easy authentication & token refresh when using echo there is another lib based on gocloak called gocloak-echo. This lib provides handler & middleware to help out, but is still in a more WIP state.
The library also provides decoding of accessTokens into custom claims
Disclosure: I am the (main) author of gocloak, so it's also a little advertising, but in general it answers the question. I had the same problem as the author and i decided to create my own lib (based on the lib of someone else, as stated in the readme on github).