logto

package
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Jul 25, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package logto is the Logto adapter behind the auth-engine provider seam.

Logto is the only identity-provider implementation in v1 and no second provider is planned, but it still lives behind github.com/AtomiCloud/diene.go-auth-engine/lib/authengine's Provider interface: that is what keeps Logto's HTTP shape out of the engine's decisions and lets consumer tests drive minting, rotation, and claim write-back without a live tenant.

Every platform that needs authentication runs its OWN Logto instance with its own issuer and its own user pool — they are auth-isolated by design. This package therefore takes its endpoints as configuration and never assumes a shared tenant.

The deployed Logto is a maintained fork driven by a declarative operator; this package only speaks its API and owns none of its configuration plane.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Client

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

Client is the Logto implementation of the auth-engine provider seam.

It converts between Logto's OAuth wire shapes and the engine's domain types, and it is the ONLY place in this module that speaks HTTP to an identity provider. Two conversions are load-bearing: the provider reports a relative `expires_in`, which this adapter turns into an absolute instant off the injected clock so caching is testable, and the provider reports failures as OAuth error documents, which this adapter turns into the engine's problem vocabulary so a caller never has to parse a provider-specific body.

func NewClient

func NewClient(options ClientOptions) (Client, error)

NewClient creates the Logto adapter, rejecting a configuration missing the endpoints it needs.

Example
package main

import (
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"

	"github.com/AtomiCloud/diene.go-auth-engine/lib/authengine"
	"github.com/AtomiCloud/diene.go-auth-engine/lib/logto"
	"github.com/AtomiCloud/diene.go-auth-engine/testhelper"
)

func main() {
	tenant := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, _ *http.Request) {
		writer.Header().Set("Content-Type", "application/json")
		_, _ = writer.Write([]byte(`{"access_token":"m2m","expires_in":600}`))
	}))
	defer tenant.Close()

	idp, _ := testhelper.NewFakeIDP(testhelper.FakeIDPOptions{})
	client, _ := logto.NewClient(logto.ClientOptions{
		Config: authengine.Config{Minting: authengine.MintingConfig{
			TokenEndpoint: tenant.URL + "/oidc/token",
			ClientID:      "operator",
		}},
		Problems: idp.Problems(),
		Clock:    idp.Clock(),
	})

	// The operator's machine-to-machine flow: no user session to impersonate.
	token, err := client.ClientCredentials(context.Background(), authengine.ClientCredentialsRequest{
		Resource: authengine.Resource{Name: "alcohol-zinc", Indicator: "https://api.zinc.invalid"},
	})
	fmt.Println(token.Value, token.Resource, err == nil)

	// The provider's relative expires_in becomes an absolute instant off the
	// injected clock, so caching decisions stay testable.
	fmt.Println(token.ExpiresAt.Sub(testhelper.FixedNow()))
}
Output:
m2m alcohol-zinc true
10m0s

func (Client) ClientCredentials

func (c Client) ClientCredentials(
	ctx context.Context,
	request authengine.ClientCredentialsRequest,
) (authengine.AccessToken, error)

ClientCredentials mints a machine-to-machine token for one resource.

func (Client) MintOneTimeToken

func (c Client) MintOneTimeToken(
	ctx context.Context,
	request authengine.OneTimeTokenRequest,
) (authengine.OneTimeToken, error)

MintOneTimeToken mints a single-use login token, used when a deferred deep-link nonce is redeemed.

func (Client) Refresh

func (c Client) Refresh(ctx context.Context, refreshToken string) (authengine.Session, error)

Refresh rotates a session's refresh token, returning the replacement pair.

A provider that returns no replacement refresh token is not rotating, so the presented token is carried forward rather than silently dropped — losing it would end the session on the next refresh.

func (Client) ResourceToken

func (c Client) ResourceToken(
	ctx context.Context,
	request authengine.ResourceTokenRequest,
) (authengine.AccessToken, error)

ResourceToken exchanges a session's refresh token for a token scoped to one resource. This is the per-resource token path: one round trip per resource, with the resource indicator and its scopes on the request.

func (Client) SetClaim

func (c Client) SetClaim(ctx context.Context, subject string, name string, value any) error

SetClaim writes a custom-data claim back onto the identity-provider user.

This is the OnboardSync write-back: it patches the user's custom data, which the provider's JWT customizer then emits onto issued tokens. The value is written as given; the family convention is the string "true" for a registration claim.

type ClientOptions

type ClientOptions struct {
	// Config is the engine configuration block this adapter reads its endpoints
	// and credentials from.
	Config authengine.Config
	// HTTP performs the round trips. Nil uses http.DefaultClient.
	HTTP Doer
	// Problems mints problem-typed failures.
	Problems *authengine.Problems
	// Clock is the injected time seam, used to turn the provider's relative
	// expires_in into an absolute expiry.
	Clock interfaces.System
}

ClientOptions configures a Client.

type Doer

type Doer interface {
	// Do performs request and returns its response.
	Do(request *http.Request) (*http.Response, error)
}

Doer performs the adapter's HTTP round trips. *http.Client satisfies it; a test binds a recording double or an httptest-backed client.

type RemoteJWKS

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

RemoteJWKS resolves verification keys from a remote JSON Web Key Set.

Fetch caching, refresh, and unknown-key refetch are the JWKS library's DEFAULTS: the family deliberately mandates no minimum key lifetime and no retry floor, and accepts the theoretical mid-rotation blip that comes with that rather than fighting each language's SDK. Do not add a bespoke cache here.

func NewJWKS

func NewJWKS(storage jwkset.Storage, problems *authengine.Problems) (RemoteJWKS, error)

NewJWKS wraps an already-constructed JWKS storage, for a consumer that needs to tune the library's client options itself.

func NewRemoteJWKS

func NewRemoteJWKS(
	ctx context.Context,
	uri string,
	problems *authengine.Problems,
) (RemoteJWKS, error)

NewRemoteJWKS creates a key source reading the JWKS published at uri.

The constructor performs the first fetch, so a misconfigured JWKS URI fails at startup rather than on the first request that needs a key.

Example
package main

import (
	"context"
	"fmt"

	"github.com/AtomiCloud/diene.go-auth-engine/lib/logto"
	"github.com/AtomiCloud/diene.go-auth-engine/testhelper"
)

func main() {
	// A misconfigured JWKS URI fails at startup rather than on the first request
	// that needs a key.
	idp, _ := testhelper.NewFakeIDP(testhelper.FakeIDPOptions{})

	_, err := logto.NewRemoteJWKS(context.Background(), "", idp.Problems())
	fmt.Println(err != nil)
}
Output:
true

func (RemoteJWKS) Key

func (j RemoteJWKS) Key(ctx context.Context, keyID string) (crypto.PublicKey, error)

Key returns the public verification key published for keyID, refreshing the set through the library's own unknown-key handling when it is not already cached.

Jump to

Keyboard shortcuts

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