Skip to content

alis-exchange/ideate-go

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

214 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

ideate-go

Go Reference License

Public Go client for interacting with the Alis Ideate API. This library provides type definitions and client stubs to easily integrate Alis Ideate features into your Go applications.

πŸš€ Installation

go get github.com/alis-exchange/ideate-go

πŸ“š Usage and examples

Here is a simple example of how to create a client and make a request to add a note to an idea using a collection key.

package main

import (
	"context"
	"log"

	"google.golang.org/grpc/metadata"

	"github.com/alis-exchange/ideate-go/alis/ideate"
)

func main() {
	ctx := context.Background()

	// 1. Establish a new client
	client, err := ideate.NewClient(ctx)
	if err != nil {
		log.Fatalf("failed to create client: %v", err)
	}

	// 2. Prepare the context with authentication
	// TODO: Replace with your actual user access token.
	// See the "Security Requirements" section below for details on obtaining a token.
	ctx = metadata.AppendToOutgoingContext(ctx, "authorization", "Bearer <USER_ACCESS_TOKEN>")

	// 3. Define the target (e.g., using a Collection Key generated in Ideate)
	key := "<COLLECTION_KEY>"

	// 4. Make a request
	// In this example, we are adding a note to the stream identified by the key.
	_, err = client.AddNote(ctx, &ideate.AddNoteRequest{
		Content: "Hello, world!",
		StreamTarget: &ideate.AddNoteRequest_Key{
			CollectionKey: key,
		},
	})
	if err != nil {
		log.Fatalf("failed to add note: %v", err)
	}

	log.Println("Successfully added note.")
}

Understanding Stream Targets

When adding context to Ideate, we need to know where to add it. This is done using the StreamTarget. The following table provides the available options:

Option Value Target Requirements
Collection Key The key generated by opening an existing collection in Ideate, clicking on Generate link and copying the key. A new idea will be created, added to the collection and the collection owners will be granted access to the idea, receiving an email on any new ideas added. -
Account The full account name: accounts/* A new idea is created within the specified account The user must have a seat within the account
Idea The full idea name: ideas/* A new contribution is added to the specified idea The user must have access to the idea
ContributionSession The full contribution session name: contributionSessions/* A new contribution is added to the specified contribution session The user must have been invited to the contribution session either explicitly or the session must be public

πŸ” Security Requirements

OAuth Client Registration

To ensure secure API interactions, you must register a new application with Alis:

  1. Log in to the Alis Identity Management System.
  2. Click New app.
  3. Complete the registration steps and securely store your Client ID and Client Secret.
  4. Configure the Redirect URI to handle the OAuth callback.

OAuth Flow

Authenticate users and obtain an access token using the standard OAuth 2.0 Authorization Code flow:

  1. Authorize: Redirect the user to the authorization endpoint:
    https://identity.alisx.com/authorize?client_id=<CLIENT_ID>&redirect_uri=<REDIRECT_URI>
    
  2. Grant Access: The user logs in and approves your application.
  3. Callback: The user is redirected to your <REDIRECT_URI> with an ?code=... parameter.
  4. Exchange: Swap this authorization code for an Access Token and Refresh Token.

Include the access token in the Authorization header of your gRPC calls as shown in the Usage example.

Storing Tokens

It is your responsibility to handle the access and refresh tokens in the way you want. We recommend two patterns for storing tokens, described below.

Pattern 1: Cookie Storage

This pattern is ideal for web applications where you want to manage user sessions securely. The flow involves redirecting the user to Alis Identity for authentication, handling the callback to exchange an authorization code for tokens, and storing those tokens in secure, HTTP-only cookies.

The Flow
  1. Initiate Sign-In: The user is redirected to the Alis Identity authorize endpoint.
  2. User Authentication: The user logs in and grants permission to your application.
  3. Authorization Callback: The user is redirected back to your application with a temporary authorization code.
  4. Token Exchange: Your server exchanges the code for an Access Token and a Refresh Token.
  5. Secure Storage: The tokens are stored as cookies in the user's browser.
  6. Token Refresh: When the Access Token expires, the Refresh Token is used to obtain a new one without requiring the user to log in again.
Implementation Example

The following example demonstrates how to implement these endpoints using Go's standard library.

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"time"
)

const (
	AlisIdentityHost         = "https://identity.alisx.com"
	IdeateClientID           = "<YOUR_CLIENT_ID>"
	IdeateClientSecret       = "<YOUR_CLIENT_SECRET>"
	IdeateAccessTokenCookie  = "ideate_access_token"
	IdeateRefreshTokenCookie = "ideate_refresh_token"
)

func main() {
	// Endpoint to start the OAuth flow
	http.HandleFunc("/auth/ideate/signin", IdeateSignin)
	// Endpoint for the OAuth callback
	http.HandleFunc("/auth/ideate/callback", IdeateCallback)
	// Endpoint to refresh the access token
	http.HandleFunc("/auth/ideate/refresh", IdeateRefresh)

    // Start your server...
}

// IdeateSignin redirects the user to Alis Identity.
// Trigger this endpoint when a user clicks "Sign in with Alis".
func IdeateSignin(w http.ResponseWriter, r *http.Request) {
    // Construct the redirect URL for the OAuth flow
	redirectURI := fmt.Sprintf("https://%s/auth/ideate/callback", r.Host)
	authorizeURL := fmt.Sprintf("%s/authorize?client_id=%s&redirect_uri=%s",
		AlisIdentityHost, IdeateClientID, redirectURI)

	// Redirect the user to Alis Identity
	http.Redirect(w, r, authorizeURL, http.StatusTemporaryRedirect)
}

// IdeateCallback handles the redirection from Alis Identity after the user authenticates.
// It receives an authorization code and exchanges it for access and refresh tokens.
func IdeateCallback(w http.ResponseWriter, r *http.Request) {
	// Extract the authorization code from the query parameters
	code := r.URL.Query().Get("code")
	if code == "" {
		http.Error(w, "missing code query param", http.StatusBadRequest)
		return
	}

    // Exchange the code for tokens using the identity service
	tokens, err := getIdeateTokens(r, "authorization_code", code)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

    // Store the received tokens in secure cookies
	tokens.WriteAsCookies(w)

	// Redirect the user to the application's main page
	http.Redirect(w, r, "/ideate", http.StatusTemporaryRedirect)
}

// IdeateRefresh uses the refresh token stored in the cookie to obtain a new access token.
// Trigger this from your client when an Ideate API call returns an unauthenticated error.
func IdeateRefresh(w http.ResponseWriter, r *http.Request) {
	// Retrieve the refresh token from the cookie
	cookie, err := r.Cookie(IdeateRefreshTokenCookie)
	if err != nil {
		http.Error(w, "missing refresh token cookie", http.StatusUnauthorized)
		return
	}

    // Request new tokens using the refresh token
	tokens, err := getIdeateTokens(r, "refresh_token", cookie.Value)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

    // Update the cookies with the new tokens
	tokens.WriteAsCookies(w)
	w.WriteHeader(http.StatusOK)
}

type IdeateTokens struct {
	AccessToken  string `json:"access_token"`
	RefreshToken string `json:"refresh_token"`
}

// WriteAsCookies helper to save tokens into HTTP-only cookies.
func (t *IdeateTokens) WriteAsCookies(w http.ResponseWriter) {
	http.SetCookie(w, &http.Cookie{
		Name:     IdeateAccessTokenCookie,
		Value:    t.AccessToken,
		Path:     "/",
		HttpOnly: true, // Recommended for security
		MaxAge:   int((7 * 24 * time.Hour).Seconds()),
	})
	http.SetCookie(w, &http.Cookie{
		Name:     IdeateRefreshTokenCookie,
		Value:    t.RefreshToken,
		Path:     "/",
		HttpOnly: true, // Recommended for security
		MaxAge:   int((7 * 24 * time.Hour).Seconds()),
	})
}

// getIdeateTokens makes a POST request to the Alis Identity token endpoint.
func getIdeateTokens(r *http.Request, grantType string, grant string) (*IdeateTokens, error) {
	// define body
	type Body struct {
		GrantType    string `json:"grant_type"` // authorization_code or refresh_token
		Code         string `json:"code,omitempty"`
		RefreshToken string `json:"refresh_token,omitempty"`
		ClientID     string `json:"client_id"`
		ClientSecret string `json:"client_secret"`
		RedirectURI  string `json:"redirect_uri,omitempty"`
	}

	// build body
	body := &Body{
		GrantType:    grantType,
		ClientID:     IdeateClientID,
		ClientSecret: IdeateClientSecret,
	}
	if grantType == "authorization_code" {
		body.Code = grant
		body.RedirectURI = fmt.Sprintf("https://%s/auth/ideate/callback", r.Host)
	} else { // refresh_token
		body.RefreshToken = grant
	}

    jsonBody, err := json.Marshal(body)
    if err != nil {
        return nil, err
    }

    resp, err := http.Post(AlisIdentityHost+"/token", "application/json", bytes.NewBuffer(jsonBody))
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return nil, fmt.Errorf("identity server error: %s", resp.Status)
    }

	tokens := &IdeateTokens{}
	if err := json.NewDecoder(resp.Body).Decode(tokens); err != nil {
		return nil, err
	}
	return tokens, nil
}
When to trigger these endpoints
  • /signin: Link your "Login" button to this endpoint.
  • /callback: This is your registered redirect URI. It handles the logic after user approval.
  • /refresh: Call this from your frontend or client-side logic whenever a request to Ideate fails with an authentication error (e.g., gRPC code Unauthenticated).

Alis Build Identity Connectors

Prerequisites This pattern requires your application to be built on the Alis Build platform, and requires the usage of the Users Management Block.

...

🀝 Contributing

Contributions are welcome! Please feel free to open issues or submit pull requests.

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.


About

Public protocol buffer stubs to interact with Ideate

Resources

License

Stars

Watchers

Forks

Contributors

Languages