sams

package module
v0.0.0-...-c1e532c Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 1, 2024 License: Apache-2.0 Imports: 13 Imported by: 0

README

Sourcegraph Accounts SDK for Go

Go Reference Go

This repository contains the Go SDK for integrating with Sourcegraph Accounts Management System (SAMS).

go get github.com/sourcegraph/sourcegraph-accounts-sdk-go

[!note] Please submit all issues to the sourcegraph/sourcegraph-accounts repository

Authentication

The following example demonstrates how to use the SDK to set up user authentication flow with SAMS for your service.

In particular,

  • The route /auth/login is where the user should be redirected to start a new authentication flow.
  • The route /auth/callback is where the user will be redirected back to the service after completing the authentication on the SAMS side.
package main

import (
	"log"
	"net/http"
	"os"

	samsauth "github.com/sourcegraph/sourcegraph-accounts-sdk-go/auth"
	"github.com/sourcegraph/sourcegraph-accounts-sdk-go/scopes"
)

type secretStore struct{
	// Authentication state is the unique identifier that is randomly-generated and
	// assigned to a particular authentication flow, they are used to prevent
	// authentication interception attacks and considered secrets, therefore it MUST
	// be stored in a backend component (e.g. Redis, database). The design of the
	// samsauth.StateStore interface explicitly disallowed storing state in the
	// cookie, as they can be tampered with when cookie values are stored
	// unencrypted.
	//
	// Authentication nonce is a unique identifier that is randomly-generated to
	// make sure the ID Token we get back from SAMS is intended for the same
	// authentication flow that we started. It is also a secret and MUST be stored
	// in a backend component.
}

func (s *secretStore) SetState(r *http.Request, state string) error {
	// TODO: Save state to session data.
	return nil
}

func (s *secretStore) GetState(r *http.Request) (string, error) {
	// TODO: Retrieve state from session data.
	return "", nil
}

func (s *secretStore) DeleteState(r *http.Request) {
	// TODO: Delete state from session data.
}

func (s *secretStore) SetNonce(r *http.Request, nonce string) error {
	// TODO: Save nonce to session data.
	return nil
}

func (s *secretStore) GetNonce(r *http.Request) (string, error) {
	// TODO: Retrieve nonce from session data.
	return "", nil
}

func (s *secretStore) DeleteNonce(r *http.Request) {
	// TODO: Delete nonce from session data.
}

func main() {
	samsauthHandler, err := samsauth.NewHandler(
		samsauth.Config{
			Issuer:         "https://accounts.sourcegraph.com",
			ClientID:       os.Getenv("SAMS_CLIENT_ID"),
			ClientSecret:   os.Getenv("SAMS_CLIENT_SECRET"),
			// RequestScopes needs to include all the scopes that the service needs to
			// access on behalf of the user. Scopes that are only used for Clients API are
			// not needed here.
			RequestScopes:  []scopes.Scope{scopes.OpenID, scopes.Email, scopes.Profile},
			RedirectURI:    os.Getenv("SAMS_REDIRECT_URI"),
			FailureHandler: samsauth.DefaultFailureHandler,
			StateStore:     &stateStore{},
		},
	)
	if err != nil {
		log.Fatal(err)
	}

	mux := http.NewServeMux()
	mux.Handle("/auth/login", samsauthHandler.LoginHandler())
	mux.Handle("/auth/callback", samsauthHandler.CallbackHandler(
		// The SAMS auth handler will handle the callback and complete the
		// authentication flow. And if successful, the `samsauth.UserInfo` will be
		// accessible from the request context.
		//
		// You can safely assume the `samsauth.UserInfo` will be present when this
		// user-supplied handler is being invoked. If any error is encountered, the
		// provided FailureHandler will be invoked instead.
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			userInfo := samsauth.UserInfoFromContext(r.Context())
			// TODO: Save user info to somewhere.
		}),
	))

	// Continue setting up your server and use the mux.
}

Clients API v1

The SAMS Clients API is for SAMS clients to obtain information directly from SAMS. For example, authorizing a request based on the scopes attached to a token. Or looking up a user's profile information based on the SAMS external account ID.

package main

import (
	"context"
	"fmt"
	"log"
	"os"

	sams "github.com/sourcegraph/sourcegraph-accounts-sdk-go"
	"github.com/sourcegraph/sourcegraph-accounts-sdk-go/scopes"
)

func main() {
	samsClient, err := sams.NewClientV1(sams.ClientV1Config{
		ConnConfig:   sams.NewConnConfigFromEnv(/* ... */),
		ClientID:     os.Getenv("SAMS_CLIENT_ID"),
		ClientSecret: os.Getenv("SAMS_CLIENT_SECRET"),
		Scopes:       []scopes.Scope{
			scopes.OpenID,
			scopes.Profile,
			scopes.Email,
			"sams::user.roles::read",
			"sams::session::read",
		},
	})
	if err != nil {
		log.Fatal(err)
	}
	
	user, err := samsClient.Users().GetUserByID(context.Background(), "user-id")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(user)
}

SAMS Accounts API

The SAMS Accounts API is for user-oriented operations like inspecting your own account details. These APIs are much simpler in nature, as most integrations will make use of the Clients API. However, the Accounts API is required if the service is not governing access based on the SAMS token scope, but instead using its own authorization mechanism. e.g. governing access based on the SAMS external account ID.

package main

import (
	"context"
	"fmt"
	"log"
	"os"

	"golang.org/x/oauth2"
	sams "github.com/sourcegraph/sourcegraph-accounts-sdk-go"
)

func main() {
	// e.g. the SAMS token prefixed with "sams_at_".
	rawToken := os.Getenv("SAMS_USER_ACCESS_TOKEN")

	// If you have the SAMS user's Refresh token, using the oauth2.TokenSource abstraction
	// will take care of creating short-lived access tokens as needed. But if you only have
	// the access token, you will need to use a StaticTokenSource instead.
	token := oauth2.Token{
		AccessToken: rawToken,
	}
	tokenSource := oauth2.StaticTokenSource(t)

	client := sams.NewAccountsV1(sams.AccountsV1Config{
		ConnConfig:  sams.NewConnConfigFromEnv(/* ... */),
		TokenSource: tokenSource,
	})
	user, err := client.GetUser(ctx)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("User Details: %+v", user)
}

Development

Buf and Connect are used for gRPC and Protocol Buffers code generation.

go install github.com/bufbuild/buf/cmd/buf@latest
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install connectrpc.com/connect/cmd/protoc-gen-connect-go@latest

After making any changes to the .proto files, in the direction that contains the buf.gen.yaml file, run:

buf generate

Documentation

Index

Constants

View Source
const DefaultExternalURL = "https://accounts.sourcegraph.com"

Variables

View Source
var (
	ErrNotFound       = errors.New("not found")
	ErrRecordMismatch = errors.New("record mismatch")
)

Functions

func ClientCredentialsTokenSource

func ClientCredentialsTokenSource(conn ConnConfig, clientID, clientSecret string, requestScopes []scopes.Scope) oauth2.TokenSource

ClientCredentialsTokenSource returns a TokenSource that generates an access token using the client credentials flow. Internally, the token returned is reused. So that new tokens are only created when needed. (Provided this `Client` is long-lived.)

The `requestScopes` is a list of scopes to be requested for this token source. Scopes should be defined using the available scopes package. All requested scopes must be allowed by the registered client - see: https://sourcegraph.notion.site/6cc4a1bd9cb247eea9674dbf9d5ce8c3

func NewAccountsV1

func NewAccountsV1(config AccountsV1Config) (*accountsv1.Client, error)

NewAccountsV1 returns a new SAMS client for interacting with Accounts API V1.

Types

type AccountsV1Config

type AccountsV1Config struct {
	ConnConfig
	// TokenSource is the OAuth2 token source to use for authentication. It MUST be
	// based on a per-user token that is on behalf of a SAMS user.
	//
	// If you have the SAMS user's refresh token, using the oauth2.TokenSource
	// abstraction will take care of creating short-lived access tokens and refresh
	// as needed. But if you only have the access token, you will need to use a
	// StaticTokenSource instead.
	TokenSource oauth2.TokenSource
}

func (AccountsV1Config) Validate

func (c AccountsV1Config) Validate() error

type ClientV1

type ClientV1 struct {
	// contains filtered or unexported fields
}

ClientV1 provides helpers to talk to a SAMS instance via Clients API v1.

func NewClientV1

func NewClientV1(config ClientV1Config) (*ClientV1, error)

NewClientV1 returns a new SAMS client for interacting with Clients API v1 using the given client credentials, and the scopes are used to as requested scopes for access tokens that are issued to this client.

func (*ClientV1) Sessions

func (c *ClientV1) Sessions() *SessionsServiceV1

Sessions returns a client handler to interact with the SessionsServiceV1 API.

func (*ClientV1) Tokens

func (c *ClientV1) Tokens() *TokensServiceV1

Tokens returns a client handler to interact with the TokensServiceV1 API.

func (*ClientV1) Users

func (c *ClientV1) Users() *UsersServiceV1

Users returns a client handler to interact with the UsersServiceV1 API.

type ClientV1Config

type ClientV1Config struct {
	ConnConfig
	// TokenSource is the OAuth2 token source to use for authentication. It MUST be
	// based on a per-client token that is on behalf of a SAMS client (i.e. Clients
	// Credentials).
	//
	// The oauth2.TokenSource abstraction will take care of creating short-lived
	// access tokens as needed.
	TokenSource oauth2.TokenSource
}

func (ClientV1Config) Validate

func (c ClientV1Config) Validate() error

type ConnConfig

type ConnConfig struct {
	// ExternalURL is the configured default external ExternalURL of the relevant Sourcegraph
	// Accounts instance.
	ExternalURL string
	// APIURL is the URL to use for Sourcegraph Accounts API interactions. This
	// can be set to some internal URLs for private networking. If this is nil,
	// the client will fall back to ExternalURL instead.
	APIURL *string
}

ConnConfig is the basic configuration for connecting to a Sourcegraph Accounts instance. Callers SHOULD use sams.NewConnConfigFromEnv(...) to construct this where possible to ensure connection configuration is unified across SAMS clients for ease of operation by Core Services team.

func NewConnConfigFromEnv

func NewConnConfigFromEnv(env envGetter) ConnConfig

NewConnConfigFromEnv initializes configuration for connecting to Sourcegraph Accounts using default standards for loading environment variables. This allows the Core Services team to more easily configure access.

func (ConnConfig) Validate

func (c ConnConfig) Validate() error

type IntrospectTokenResponse

type IntrospectTokenResponse struct {
	// Active indicates whether the token is currently active. The value is "true"
	// if the token has been issued by the SAMS instance, has not been revoked, and
	// has not expired.
	Active bool
	// Scopes is the list of scopes granted by the token.
	Scopes scopes.Scopes
	// ClientID is the identifier of the SAMS client that the token was issued to.
	ClientID string
	// ExpiresAt indicates when the token expires.
	ExpiresAt time.Time
}

type SessionsServiceV1

type SessionsServiceV1 struct {
	// contains filtered or unexported fields
}

SessionsServiceV1 provides client methods to interact with the SessionsService API v1.

func (*SessionsServiceV1) GetSessionByID

func (s *SessionsServiceV1) GetSessionByID(ctx context.Context, id string) (*clientsv1.Session, error)

GetSessionByID returns the SAMS session with the given ID. It returns ErrNotFound if no such session exists. The session's `User` field is populated if the session is authenticated by a user.

Required scope: sams::session::read

func (*SessionsServiceV1) SignOutSession

func (s *SessionsServiceV1) SignOutSession(ctx context.Context, sessionID, userID string) error

SignOutSession revokes the authenticated state of the session with the given ID for the given user. It does not return error if the session does not exist or is not authenticated. It returns ErrRecordMismatch if the session is authenticated by a different user than the given user.

Required scope: sams::session::write

type TokensServiceV1

type TokensServiceV1 struct {
	// contains filtered or unexported fields
}

TokensServiceV1 provides client methods to interact with the TokensService API v1.

func (*TokensServiceV1) IntrospectToken

func (s *TokensServiceV1) IntrospectToken(ctx context.Context, token string) (*IntrospectTokenResponse, error)

IntrospectToken takes a SAMS access token and returns relevant metadata.

🚨SECURITY: SAMS will return a successful result if the token is valid, but is no longer active. It is critical that the caller not honor tokens where `.Active == false`.

type UsersServiceV1

type UsersServiceV1 struct {
	// contains filtered or unexported fields
}

UsersServiceV1 provides client methods to interact with the UsersService API v1.

func (*UsersServiceV1) GetUserByID

func (s *UsersServiceV1) GetUserByID(ctx context.Context, id string) (*clientsv1.User, error)

GetUserByID returns the SAMS user with the given ID. It returns ErrNotFound if no such user exists.

Required scope: profile

func (*UsersServiceV1) GetUserRolesByID

func (s *UsersServiceV1) GetUserRolesByID(ctx context.Context, userID, service string) ([]string, error)

GetUserRolesByID returns all roles that have been assigned to the SAMS user with the given ID and scoped by the service.

Required scopes: sams::user.roles::read

func (*UsersServiceV1) GetUsersByIDs

func (s *UsersServiceV1) GetUsersByIDs(ctx context.Context, ids []string) ([]*clientsv1.User, error)

GetUsersByIDs returns the list of SAMS users matching the provided IDs.

NOTE: It silently ignores any invalid user IDs, i.e. the length of the return slice may be less than the length of the input slice.

Required scopes: profile

Directories

Path Synopsis
accounts
v1
clients
v1

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL