console

package
v1.77.0 Latest Latest
Warning

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

Go to latest
Published: Oct 19, 2026 License: MIT Imports: 21 Imported by: 0

Documentation

Overview

Package console is the Google module's half of the console: the Workspace service (the directories an operator connects), the consent and the key upload that connect one, and the sign-in's Identify. The console in the issuer authenticates a person and asks this package, as the caller class `console` (consolecall); the module holds the OAuth client secret, the service-account keys, the workspace records and the refresh writer, and the issuer holds none of them (docs/decisions/0071, v1.76 D-v176-1 b).

The sign-in is the one method the issuer itself calls (`google.identify`, caller class `issuer`): it needs the client secret, once per login.

Index

Constants

View Source
const (
	MethodListWorkspaces   = "console.list_workspaces"
	MethodBeginConnect     = "console.begin_connect"
	MethodReconnect        = "console.reconnect"
	MethodCompleteConsent  = "console.complete_consent"
	MethodUploadKey        = "console.upload_key"
	MethodSetServedDomains = "console.set_served_domains"
	MethodSetSyncedGroups  = "console.set_synced_groups"
	MethodProbe            = "console.probe"
	MethodRefresh          = "console.refresh"
	MethodDisconnect       = "console.disconnect"

	// MethodIdentify turns a sign-in's code into the address that authenticated.
	// It answers the caller class `issuer` and no other: it needs the OAuth
	// client secret, and the console has no business with a sign-in.
	MethodIdentify = "identify"
	// MethodSignInURL is where the browser goes to sign in. It answers the
	// issuer alone, like [MethodIdentify]: the module holds the OAuth client, so
	// the issuer holds nothing to build the address from.
	MethodSignInURL = "sign_in_url"
)

The console methods, answered to the caller class `console` alone (consolecall.MethodPrefix), and the two the issuer calls.

View Source
const (
	CodeInvalidArgument    = "invalid_argument"
	CodeNotFound           = "not_found"
	CodeFailedPrecondition = "failed_precondition"
	CodeUnimplemented      = "unimplemented"
	CodeUnauthenticated    = "unauthenticated"
)

The codes the module answers with besides modcall's own: each is a refusal of the person's request, and its message is safe to show them.

View Source
const CallerIssuer = "issuer"

CallerIssuer is the caller class of the issuer, the only one that signs somebody in.

View Source
const CompleteDeadline = 25 * time.Second

CompleteDeadline bounds the consent's completion: the exchange with Google and the first read of the directory happen in one call, and the browser is waiting on it.

View Source
const MaxKeyBytes = 64 << 10

MaxKeyBytes bounds a service-account key uploaded through the console. A key file is about 2 KB; the bound is generous and exists so that nothing the issuer forwards can be large.

Module is the module the methods belong to.

View Source
const RefusalText = "not authorised"

RefusalText is what a caller who may not is told, whatever the reason. The reason is the module's log's.

Variables

View Source
var ConsentCauses = []string{
	"The directory API is not enabled in the cloud project that owns this hub's OAuth client. " +
		"The message above names the project and the page that enables it.",
	"The API was enabled moments ago. Enabling propagates over a few minutes; try again.",
	"The account that consented is not an administrator of the workspace, or lacks the " +
		"privileges to read users and groups.",
	"The consent was granted with a personal account, or with an account in a different " +
		"workspace than the one intended. The account chooser remembers the last one used.",
}

ConsentCauses is what to check when a directory refuses the credential its own consent screen has just granted. Ordered by how often each one is the answer, because a list read top-down is a list whose order is a claim about likelihood.

View Source
var ConsentRedirects = map[string]string{
	"google": google.CallbackPath,
}

ConsentRedirects is where a backend's CONSENT flow comes back: always the console, because connecting a directory is the console's job -- but at the HOST ROOT, never under route.pathPrefix. /connect is a bootstrap path, served on its own HTTPRoute precisely so the operator connecting the FIRST directory -- who by definition no directory can vouch for yet -- is not sent through whatever gates the console's own path. A redirect URI registered under the prefix would be one Google never returns to.

Read from the backend for the same reason the scopes are: a copy would drift, and the drift surfaces in a cloud console's own words, much later, naming nothing useful.

View Source
var ErrRemote = errors.New("google: this belongs to the Google module, which the console reaches by a call")

ErrRemote is what a connector method that belongs to the module answers.

View Source
var Scopes = map[string][]string{
	"google": google.Scopes,
}

Scopes is what each backend is asked for, all read-only. It reads the list from the backend rather than repeating it: a copy would drift, and the console would tell an operator to grant one set while the hub asked for another.

View Source
var SignInRedirects = map[string]string{
	"google": google.SignInCallbackPath,
}

SignInRedirects is where a backend's SIGN-IN flow comes back, which is a different service. With one shared OAuth client -- one client for the hub and the issuer, so there is one secret to rotate -- the sign-in redirect belongs to the ISSUER's hostname, not the console's.

Functions

func BackendEnum

func BackendEnum(kind string) directoryrosterv1.Backend

BackendEnum is BackendKind's inverse.

func BackendKind

func BackendKind(b directoryrosterv1.Backend) string

BackendKind turns the contract's enum into a backend's own name.

func ConnectError

func ConnectError(err error) error

ConnectError is an error of the module as the console's Connect handler returns it. The module's refusals keep their fixed words; a transport failure says the module could not be reached and nothing else, since what went wrong is the log's.

func DomainsProto

func DomainsProto(domains []hub.DomainStanding) []*directoryrosterv1.WorkspaceDomain

DomainsProto is a workspace's domains as the contract tells them.

func ProviderName

func ProviderName(kind string) string

ProviderName is a backend as a person calls it.

func Register

func Register(s *modcall.Server, svc *Service)

Register makes the service answer on s: the console methods to the caller class `console` alone, and the two the issuer calls to the class `issuer` alone. A method that failed with something the person should read keeps its words; any other failure is the transport's, with no detail.

func RegisterLazy

func RegisterLazy(s *modcall.Server, log *slog.Logger, open func(context.Context) (*Service, error))

RegisterLazy is Register for a function that opens the service when the first call arrives: a function whose other invocations (a scheduled refresh) do not need the hub, the connectors or the secrets this assembles. An open that fails is told as the module being unavailable, and tried again by the next call. log is where a failed open is written.

func Stamp

func Stamp(t time.Time) *timestamppb.Timestamp

Stamp is a time as the contract carries it: the zero time is nil.

func WorkspaceProto

func WorkspaceProto(v *hub.WorkspaceView) *directoryrosterv1.Workspace

WorkspaceProto is a connected workspace as the contract tells it.

Types

type BeginRequest

type BeginRequest struct {
	Backend string `json:"backend,omitempty"`
	Bind    string `json:"bind,omitempty"`
	State   string `json:"state"`
}

BeginRequest starts a consent. State is the issuer's signed state, which the provider hands back at the callback; the module never signs one. Backend names the directory for a new connection; Bind names the workspace being reconnected (and Backend is then the one it was connected with).

type BeginResponse

type BeginResponse struct {
	URL string `json:"url"`
}

BeginResponse is where the browser goes to consent.

type Client

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

Client is the console's view of the module: one method per console method, each sent as the caller class `console`.

func NewClient

func NewClient(c modcall.Caller) *Client

NewClient is the console's calls to the Google module through c, which sends as the class `console`.

func (*Client) BeginConnect

func (c *Client) BeginConnect(ctx context.Context, caller consolecall.Caller, kind, state string) (string, error)

BeginConnect asks where the browser goes to consent to a new connection.

func (*Client) CompleteConsent

func (c *Client) CompleteConsent(ctx context.Context, caller consolecall.Caller, req CompleteRequest) (CompleteResponse, error)

CompleteConsent forwards the callback's code. It is never retried: the code is single-use, so a second send after a lost answer would find it spent and report a failure for a connection that was made. It is bounded by CompleteDeadline.

func (*Client) Disconnect

Disconnect implements the console's call of the same name.

func (*Client) ListWorkspaces

ListWorkspaces implements the console's call of the same name.

func (*Client) Probe

Probe implements the console's call of the same name.

func (*Client) Reconnect

func (c *Client) Reconnect(ctx context.Context, caller consolecall.Caller, workspace, state string) (string, error)

Reconnect asks the same for an existing workspace.

func (*Client) Refresh

Refresh implements the console's call of the same name.

func (*Client) SetServedDomains

SetServedDomains implements the console's call of the same name.

func (*Client) SetSyncedGroups

SetSyncedGroups implements the console's call of the same name.

func (*Client) UploadKey

UploadKey forwards a service-account key. The key is in memory for the length of the call and is not written anywhere here; more than MaxKeyBytes is refused before it is sent.

type ClientVerifier

type ClientVerifier interface {
	VerifyClient(ctx context.Context) error
}

ClientVerifier is a connector that can say whether this installation's registered client would be accepted, without a person. A connector that cannot simply does not implement it, and the flow starts unchecked.

type CompleteRequest

type CompleteRequest struct {
	Backend string `json:"backend"`
	Code    string `json:"code"`
	Bind    string `json:"bind,omitempty"`
}

CompleteRequest is the consent's callback: the provider's code, which is single-use, and the workspace being reconnected, if any.

type CompleteResponse

type CompleteResponse struct {
	Workspace string `json:"workspace"`
	Backend   string `json:"backend"`
}

CompleteResponse is the workspace the consent connected.

type Connector

type Connector interface {
	// Kind names the backend, matching backend.Backend.Kind.
	Kind() string
	// AuthURL is where the browser goes to consent. It fails when this
	// deployment cannot start a consent yet — most often because nobody
	// has registered an OAuth client — because a button that navigates
	// nowhere is worse than one that says why.
	AuthURL(state string) (string, error)
	// Exchange turns the callback's code into a workspace and the backend
	// that reads it. Bind carries the workspace being reconnected, or is
	// empty for a new connection.
	Exchange(ctx context.Context, code, bind string) (hub.Workspace, backend.Backend, error)
}

Connector starts and finishes an admin-consent flow for one backend. The browser is in the middle of it, which is why the callback is a plain HTTP route rather than an RPC.

type CredentialReopener

type CredentialReopener interface {
	OpenStored(ctx context.Context, cred backend.Credential) (backend.Backend, error)
}

CredentialReopener is a connector that can turn a stored credential back into a reader -- the counterpart of Exchange and FromKey for a process that starts already holding the secret, rather than mid-flow with a browser. A connector that cannot simply does not implement it, and a kind with no such connector is refused by name after a restart, the same as it would be refused at the console.

type KeyConnector

type KeyConnector interface {
	FromKey(ctx context.Context, key []byte, admin string) (hub.Workspace, backend.Backend, error)
}

KeyConnector is the second way in: a service-account key uploaded instead of a consent flow. A connector that cannot do it simply does not implement this.

type RemoteConnector

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

RemoteConnector is the Google connector as the issuer holds it when the module is another function: it offers the kind, and the sign-in through the module (`google.sign_in_url`, `google.identify`). The consent and the key upload are the module's, reached through Client; this connector never holds a client secret or a key, and its Exchange and FromKey refuse.

func NewRemoteConnector

func NewRemoteConnector(c modcall.Caller) *RemoteConnector

NewRemoteConnector is the connector over c, which sends as the caller class `issuer`.

func (*RemoteConnector) AuthURL

func (*RemoteConnector) AuthURL(string) (string, error)

AuthURL implements Connector: the consent is begun at the module.

func (*RemoteConnector) Exchange

Exchange implements Connector: the code is forwarded to the module.

func (*RemoteConnector) FromKey

FromKey implements KeyConnector: a key is forwarded to the module.

func (*RemoteConnector) Identify

func (r *RemoteConnector) Identify(ctx context.Context, code string) (string, error)

Identify implements SignInConnector with `google.identify`. A module that is cold answers late and one that is gone answers not at all, so the error is the caller's to show: a sign-in cannot be completed without it.

func (*RemoteConnector) Kind

func (*RemoteConnector) Kind() string

Kind implements Connector.

func (*RemoteConnector) SignInURL

func (r *RemoteConnector) SignInURL(state string) (string, error)

SignInURL implements SignInConnector: the module holds the client the address is built from.

type Service

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

Service is the Workspace service on the module's side: the hub that holds the workspaces, the connectors that open them, and the trail it records what it did to. It acts on the caller snapshot of the console, never on a session.

func NewService

func NewService(h *hub.Hub, connectors []Connector, rec audit.Recorder, log *slog.Logger) *Service

NewService is the Workspace service over a hub and its connectors. A nil recorder records nothing, a nil logger is the default one.

func (*Service) BeginConnect

func (s *Service) BeginConnect(ctx context.Context, c consolecall.Caller, req BeginRequest) (BeginResponse, error)

BeginConnect returns where the browser goes to consent to a new connection.

func (*Service) CompleteConsent

func (s *Service) CompleteConsent(ctx context.Context, c consolecall.Caller, req CompleteRequest) (CompleteResponse, error)

CompleteConsent finishes a consent: it exchanges the provider's code, checks it is the tenant being reconnected if one is, and adopts the workspace. The code is single-use, so the issuer never retries this call. What a person should read when it fails is a Problem, not an error.

func (*Service) Disconnect

Disconnect removes a workspace.

func (*Service) Identify

func (s *Service) Identify(ctx context.Context, code string) (string, error)

Identify turns the sign-in's code into the address that authenticated. It answers the issuer alone (Register); what that address may do is decided by the directory and the policy, not here.

func (*Service) ListWorkspaces

ListWorkspaces lists the workspaces the caller may see.

func (*Service) Probe

Probe checks a workspace's credential now.

func (*Service) Reconnect

Reconnect is the same flow, bound to an existing workspace.

func (*Service) Refresh

Refresh reads a workspace's directory now, whatever the age of its snapshot.

func (*Service) Server

func (s *Service) Server() *modcall.Server

Server is a module server with the service registered on it.

func (*Service) SetServedDomains

SetServedDomains sets which of a tenant's domains this hub answers for.

func (*Service) SetSyncedGroups

SetSyncedGroups sets which of a tenant's groups this hub keeps.

func (*Service) SignInURL

func (s *Service) SignInURL(state string) (string, error)

SignInURL is where the browser goes to sign in.

func (*Service) UploadKey

UploadKey connects a workspace with a service-account key. The key is in this call's memory and the credential store's, nowhere else: it is never logged, and a failure names the cause without it.

type SignInConnector

type SignInConnector interface {
	Connector
	// SignInURL is where the browser goes to prove who somebody is.
	SignInURL(state string) (string, error)
	// Identify turns the callback's code into the address that
	// authenticated. Nothing else is taken from it: what that address may
	// do is decided by the directory and the policy.
	Identify(ctx context.Context, code string) (string, error)
}

SignInConnector is a connector that can also say who somebody is.

It is a different flow from Connector, not a parameter of it: consent is an administrator granting this hub read access to a company, and asks for the directory scopes; sign-in is a person proving who they are, and asks for nothing but their address. They return to different endpoints because the two endpoints have opposite authorisation — one adopts a workspace and demands an operator, the other is how a person becomes anyone at all.

Jump to

Keyboard shortcuts

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