kal

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Aug 6, 2026 License: MIT Imports: 20 Imported by: 0

README

kal

Authentication and authorization for gqlgen applications on Postgres, as an embedded library rather than a service. Built to sit alongside luima.

Sessions in your database. Identity in the request context. Authorization in the WHERE clause.

Three positions, each one taken against how the rest of the Go ecosystem does it.

Sessions in your database. Opaque server-side sessions, so revoking one is an UPDATE, "log out everywhere" is one statement, and a user can list their own devices — all things that are structurally unimplementable with stateless-only tokens. Kratos, SuperTokens and Zitadel are separate services with separate databases, which costs you the JOIN, the shared transaction, and a second backup and migration story. And because the session cookie is the long-lived credential, kal ships no refresh token at all: nothing to rotate, no reuse-detection family, no two-tab race. That entire subsystem — the largest single chunk of every JWT-first auth library — does not exist here.

Identity in the request context. The middleware is net/http, mounted inside luima's Fiber adaptor, so a resolver reads a typed *kal.Principal from its own ctx. Anonymous is not an error and the middleware never returns 401: one GraphQL endpoint serves public and private fields in the same document, so the graph decides.

Authorization in the WHERE clause. Every authorization library in Go answers "may Alice read document 7". None answers "which documents may Alice read" without N checks or a thousand-item ID list. kal.Scope composes the caller's ownership predicate into the statement, which answers both and applies to a DELETE without a read-then-check round trip that has a TOCTOU window.

Install

go get github.com/ulas96/kal

Requires luima ≥ 0.2.0 for the HTTPMiddleware, Configure and scoped-crud seams — kal is built and tested against v0.2.1. Postgres 13 or newer (gen_random_uuid() is built in from 13).

Wiring

auth, err := kal.New(kal.Config{
    DB:      db,                          // *pg.DB
    BaseURL: "https://app.example.com",   // where emailed links point
    Mailer:  myMailer,                    // one Send method; kal ships no SMTP client
})
if err != nil {
    log.Fatal(err)
}

c := generated.Config{Resolvers: &graph.Resolver{DB: db, Auth: auth}}
c.Directives.Auth = auth.Directive()

app := luima.New(luima.Config{
    Schema:         generated.NewExecutableSchema(c),
    HTTPMiddleware: []func(http.Handler) http.Handler{auth.Middleware()},
    Configure:      auth.Configure(),
    ErrorPresenter: kal.PresentError,
})

Apply the schema with your own migration tool — the SQL is plain files behind an embed.FS (migrations.FS), or auth.Migrate(ctx) runs them in order if you have no tooling yet.

Paste authz.DirectiveSDL into your .graphqls, and bind the enum in gqlgen.yml:

models:
  AuthLevel:
    model: github.com/ulas96/kal/authz.AuthLevel

A login resolver is then three lines, because the cookie travels through the context:

func (r *mutationResolver) Login(ctx context.Context, email, password string) (*model.User, error) {
    p, err := r.Auth.Accounts.Login(ctx, r.DB, email, password)
    if err != nil {
        return nil, err   // one INVALID_CREDENTIALS for every way it fails
    }
    return r.userByID(ctx, p.UserID)
}

The three authorization layers

Ship all three. Each catches what the one above it misses.

1 · The @auth directive — coarse and declarative. One composed directive, never a stack, because gqlgen chains directives inside-out and @auth @hasRole(ADMIN) runs hasRole first, which is the opposite of how everyone reads it.

type Query {
  health: String            @auth(requires: ANONYMOUS)
  me: User                  @auth
  auditLog: [Entry!]        @auth(roles: ["admin"])
  billingEmail: String      @auth(mfa: true)
}

The implementation reads only the context and never queries — a directive on a field of a list type runs once per row, so a check that costs a query is an N+1 that appears only under load.

2 · Scope — the real enforcement.

func (r *mutationResolver) DeleteDoc(ctx context.Context, id string) (bool, error) {
    return luima.Delete(ctx, r.DB, &model.Doc{ID: id}, kal.Scope(ctx, "owner_id"))
}

A row that exists but is not yours matches nothing, so the delete reports that nothing happened. "Not yours" and "does not exist" become indistinguishable, which is the correct answer to give an unauthorized caller. An anonymous caller gets a predicate matching nothing — never an open query.

Need something kal does not model? Scope returns a plain func(*orm.Query) *orm.Query, so call OpenFGA inside your own closure and return q.Where("id = any(?)", pg.Array(ids)). There is no Authorizer interface to implement.

3 · Postgres RLS — optional, and the point of it is that it survives a forgotten check in the two above.

err := auth.WithRLS(ctx, func(tx orm.DB) error { /* … */ })

Read authz.WithRLS's doc comment before writing a policy. Four things there have each silently broken a production deployment, and docs/gotchas.md lists them.

The coverage test

The single highest-value thing in this library, and it is forty lines:

func TestAuthCoverage(t *testing.T) {
    schema := generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{}})
    if err := kal.AssertAuthCoverage(schema, "Query.health", "Mutation.login"); err != nil {
        t.Fatal(err)
    }
    if err := kal.AssertDirectivesWired(generated.DirectiveRoot{Auth: auth.Directive()}); err != nil {
        t.Fatal(err)
    }
}

The failure mode of resolver-level authorization is a forgotten check, and a forgotten check is invisible: it compiles, it passes review, and it returns data. Walking the schema is the only way to see the absence of something. It reports every miss at once, and it is a test rather than a startup check so that adding a public field means a red test you annotate away — not a server that refuses to boot on a Friday.

Transport rules

kal's middleware requires every request to carry a Content-Type outside {text/plain, application/x-www-form-urlencoded, multipart/form-data}, or an X-Kal-Operation / X-Requested-With header. Those content types plus a header-free GET are exactly the CORS simple request set — what a browser sends cross-origin with cookies and no preflight. Requiring anything outside it forces a preflight the attacker's origin cannot pass. No token, no state.

Never register transport.UrlEncodedForm, transport.MultipartForm or transport.GRAPHQL while cookie authentication is on. All three are POST with CORS-simple content types and no operation-type restriction, so a cross-origin form can execute mutations with ambient cookies.

luima sets no Access-Control-Allow-Origin anywhere, so configure cors.New with an explicit origin list — never * with credentials.

What is in the box

package what it holds
kal Config, New, the guard extension, the re-export shim
authn Argon2id, registration, login, backoff, verification, reset, invite
authz Principal, @auth, Scope, AssertAuthCoverage, roles, RLS
session tokens, the store, the cookie, the middleware, the JWT leg
kalerr the error contract
zkauthn Groth16/BN254 knowledge and membership proofs, the credential tree, pseudonymous login
zkauthz request-local proven claims behind @auth(proves:)
migrations the schema, as .sql behind an embed.FS
tests every test, outside the packages it exercises
What it costs you to depend on kal

Beyond luima's graph, kal adds four direct requires — read go.mod for the pinned versions and go.sum for the full transitive set:

module why who pays
github.com/golang-jwt/jwt/v5 the optional JWT leg everyone
github.com/consensys/gnark the Groth16 circuits and prover/verifier everyone
github.com/consensys/gnark-crypto BN254 field, pairing and MiMC everyone
golang.org/x/sync the verification semaphore everyone

Argon2 costs nothing new — golang.org/x/crypto is already there.

"Who pays: everyone" is literal, and it is the part worth reading twice. gnark and gnark-crypto are direct requires in kal's go.mod, so they enter the module graph of every consumer and are recorded in every consumer's go.sum — whether or not a single line of zkauthn is imported, and whether or not Config.ZK is ever set. Leaving Config.ZK nil turns the feature off at runtime; it does not remove the modules. Neither would a build tag: build tags select which files compile, they do not change what go.mod requires. Between them the two modules pull in assembly, unsafe, and roughly a dozen transitive requires.

If that is not acceptable for your project, the honest answer is not to depend on kal today. Say so on the issue tracker — splitting zkauthn/zkauthz into a separate module is the fix, and it is not one a configuration flag can substitute for.

Deliberately not here

WebAuthn/passkeys (a second authentication system's worth of surface; auth_sessions.mfa_at and @auth(mfa:) are the seam if it is ever added), an admin UI, a scaffolding CLI, email templating beyond a one-method Mailer, avatar storage, a policy DSL, SMS as a second factor, magic links as a primary factor, and a pluggable Store interface — Postgres is the premise, so that interface would have one implementation and would forbid the JOIN that is the entire point.

OAuth/OIDC and TOTP MFA are planned as separate modules, not merely separate packages, because a separate package in this module would still put its dependencies in every consumer's graph — which is exactly what happened with zkauthn and gnark, and is described honestly above rather than repeated here as an aspiration.

Operating the ZK module

The proving key is a client artifact and kal never loads one: the server holds only verifying keys, pinned by SHA-256 in your own source. Packaging and shipping the prover — a JavaScript/WASM bundle, a mobile client, a CLI — is your responsibility and your trust boundary. A client that computes proofs also holds the member's secret, so a compromised prover bundle is a compromised credential for every member who loads it; version and pin it the way you would any other credential-handling code.

Recovery. A Knowledge secret is returned exactly once and is not recoverable. Re-enrolment is the recovery path: EnrollKnowledge replaces the commitment after re-verifying the account's password, or recent MFA when the account has no password, and revokes every other session when it does. An account with neither factor cannot self-serve and needs an operator.

Revocation. Disabling an account does not revoke its membership credential. A credential is deliberately not joined to the account that received it, so soft-deleting a user leaves their leaf live and they can still log in under a fresh pseudonym for any audience they have not used. RevokeCredentialsForUser is the operation that revokes it, and calling it is a deployment decision kal does not make for you.

Development

make test      # go test ./...          — the TestDB* tests SKIP without a database
make test-db   # same, with .env loaded — they run
make check     # fmt + vet + lint + test-db + audit
make audit     # govulncheck + gosec

A green go test ./... proves less than it looks: the TestDB* tests skip without DATABASE_URL, and a skip still reports ok. In this library that silence would cover session revocation, token single-use and the unique index. Copy .env.example to .env and run make test-db; CI pins it with a postgres:16 service container and greps --- PASS: TestDB out of the -v output.

Licence

MIT. See LICENSE.

Documentation

Overview

Package kal @notice Authentication and authorization for gqlgen applications on Postgres, as an embedded library rather than a service.

@dev Three positions, each argued against how the rest of the ecosystem does it:

**Sessions in your database.** Opaque server-side sessions, so revoking one is an UPDATE and "log out everywhere" is one statement. Kratos, SuperTokens and Zitadel take your users table into a separate service, which costs you the JOIN, the shared transaction, and a second backup and migration story. Because the session cookie is the long-lived credential, kal ships no refresh token at all — the rotating-family subsystem every JWT-first library must build does not exist here.

**Identity in the request context.** The middleware is net/http, mounted inside luima's adaptor, so a resolver reads a typed Principal from its own ctx. Anonymous is not an error: one endpoint serves public and private fields in the same document, and the graph decides.

**Authorization in the WHERE clause.** Scope composes the caller's ownership predicate into the statement. A policy engine answers "may Alice read document 7"; it does not stop a list query returning everything, and it cannot apply to a DELETE without a read-then-check round trip that has a TOCTOU window.

The packages

This package re-exports the four below, so the common case needs one import:

[github.com/ulas96/kal/authn]      passwords, registration, login, recovery
[github.com/ulas96/kal/authz]      Principal, @auth, Scope, coverage, RLS
[github.com/ulas96/kal/session]    sessions, the cookie, the middleware, the JWT leg
[github.com/ulas96/kal/kalerr]     the error contract
[github.com/ulas96/kal/migrations] the schema, as .sql behind an embed.FS

The types below are aliases, not copies, so the two spellings are interchangeable. The cost, stated plainly: a genuinely new sub-package export is invisible from here until it is added by hand, and tests/ asserts the identity of the ones that exist.

Wiring

Requires luima ≥ 0.2.0 for the HTTPMiddleware and Configure seams:

auth, err := kal.New(kal.Config{
    DB:      db,
    BaseURL: "https://app.example.com",
    Mailer:  myMailer,
})
app := luima.New(luima.Config{
    Schema:         generated.NewExecutableSchema(c),
    HTTPMiddleware: []func(http.Handler) http.Handler{auth.Middleware()},
    Configure:      auth.Configure(),
    ErrorPresenter: kal.PresentError,
})

with `c.Directives.Auth = auth.Directive()` and authz.DirectiveSDL pasted into the schema.

Index

Constants

View Source
const (
	// DefaultMaxAliases @notice Selections allowed in one document.
	DefaultMaxAliases = 100
	// DefaultMaxDepth @notice Nesting allowed in one document.
	//
	// @dev luima's ComplexityLimit does not cover this: gqlgen's complexity is per selected
	// field, so 400 levels of nesting through a cyclic schema costs about 400 and sails past a
	// 1000 limit.
	DefaultMaxDepth = 15
)

Guard defaults. Every one of them is a limit on the document, not on the request, because GraphQL executes many operations per request and an HTTP-request rate limiter counts none of them.

View Source
const (
	LevelAnonymous          = authz.LevelAnonymous
	LevelAuthenticated      = authz.LevelAuthenticated
	ZKCircuitKnowledge      = zkauthn.CircuitKnowledge
	ZKCircuitMembership     = zkauthn.CircuitMembership
	ZKClaimRecurring        = zkauthn.ClaimRecurring
	ZKClaimOneShot          = zkauthn.ClaimOneShot
	ZKMerkleDepth           = zkauthn.MerkleDepth
	ZKSecretSize            = zkauthn.SecretSize
	ZKKnowledgeConstraints  = zkauthn.KnowledgeConstraints
	ZKMembershipConstraints = zkauthn.MembershipConstraints
	ZKKnowledgeCircuitID    = zkauthn.KnowledgeCircuitID
	ZKMembershipCircuitID   = zkauthn.MembershipCircuitID
)

The AuthLevel values, re-exported so a consumer need not import authz for a switch.

Variables

This section is empty.

Functions

func AssertAuthCoverage

func AssertAuthCoverage(schema graphql.ExecutableSchema, exempt ...string) error

AssertAuthCoverage @notice Fails if any field is reachable without an @auth annotation. Call it from a test. See authz.AssertAuthCoverage.

func AssertDirectivesWired

func AssertDirectivesWired(directiveRoot any) error

AssertDirectivesWired @notice Fails if any directive implementation is nil. See authz.AssertDirectivesWired.

func HasRole

func HasRole(ctx context.Context, role string) bool

HasRole @notice Whether the caller holds the named role. See authz.HasRole.

func PresentError

func PresentError(ctx context.Context, err error) *gqlerror.Error

PresentError @notice luima's presenter plus an extensions.code for kal's errors. Set it as luima's Config.ErrorPresenter. See kalerr.PresentError.

func Scope

func Scope(ctx context.Context, column string) func(*orm.Query) *orm.Query

Scope @notice The caller's ownership predicate, for luima's crud options. See authz.Scope.

func SetupZK added in v0.2.0

func SetupZK(kind ZKCircuit, pkw, vkw io.Writer) error

SetupZK @notice Runs setup for one kal ZK circuit. See zkauthn.Setup.

func ValidatePassword

func ValidatePassword(password string) error

ValidatePassword @notice Applies the password policy. See authn.ValidatePassword.

func ZKCircuitInfo added in v0.2.0

func ZKCircuitInfo(kind ZKCircuit) (int, [32]byte, error)

func ZKKnowledgeValid added in v0.2.0

func ZKKnowledgeValid(w ZKKnowledgeWitness) bool

func ZKMembershipValid added in v0.2.0

func ZKMembershipValid(w ZKMembershipWitness) bool

func ZKProofSize added in v0.2.0

func ZKProofSize() int

Types

type Auth

type Auth struct {
	// Sessions @notice Issue, look up, rotate, revoke and list sessions.
	Sessions *session.Sessions
	// Accounts @notice Register, log in, recover, change a password.
	Accounts *authn.Accounts
	// Roles @notice Grant and revoke role membership.
	Roles *authz.Roles
	// Hasher @notice Password hashing, for importing an existing user base.
	Hasher *authn.Hasher
	// JWT @notice The optional bearer-token leg. Nil unless JWTIssuer was set.
	JWT *session.JWT
	// ZK @notice The optional proof and credential service. Nil unless Config.ZK was set.
	ZK *zkauthn.ZK
	// contains filtered or unexported fields
}

Auth @notice Everything kal exposes to an application, wired and validated.

func New

func New(cfg Config) (*Auth, error)

New @notice Validates the configuration and wires everything up.

@dev Fails loudly on anything that cannot have a safe default. Every other field has one, and every default is the production posture.

@param cfg the configuration; DB, BaseURL and Mailer are required @return *Auth the wired library @return error a description of what is missing or contradictory

func (*Auth) Configure

func (a *Auth) Configure() func(*handler.Server)

Configure @notice Applies kal's gqlgen extensions: the anti-batching guard, conditional introspection, and suggestion suppression.

Pass it to luima's Config.Configure (luima ≥ 0.2.0).

@dev SetDisableSuggestion is here rather than optional because gqlparser's "Did you mean …?" text passes straight through luima's presenter by design, so a caller guessing a field name still learns the real one with introspection off.

@return func(*handler.Server) applied immediately before the handler is mounted

func (*Auth) Directive

func (a *Auth) Directive() func(context.Context, any, graphql.Resolver, AuthLevel, []string, *bool, []string) (any, error)

Directive @notice The @auth implementation for your generated DirectiveRoot.

c.Directives.Auth = auth.Directive()

Paste authz.DirectiveSDL into your schema for the matching declaration.

func (*Auth) Middleware

func (a *Auth) Middleware() func(http.Handler) http.Handler

Middleware @notice The net/http middleware that resolves the session cookie into a Principal, carries the cookie jar, and enforces the cross-site transport guard.

Pass it to luima's Config.HTTPMiddleware (luima ≥ 0.2.0), or mount it in any net/http stack.

@return func(http.Handler) http.Handler outermost-first middleware

func (*Auth) Migrate

func (a *Auth) Migrate(ctx context.Context) error

Migrate @notice Applies every embedded migration in order.

@dev A convenience, not a migration framework: it runs the .sql files and nothing else — no version table, no down migrations, no locking. Applications with their own tooling should feed it migrations.FS instead. Each file is idempotent only in the sense that a second run fails loudly on the existing tables rather than corrupting them.

@param ctx the context for the statements @return error the first failure, naming the file

func (*Auth) WithRLS

func (a *Auth) WithRLS(ctx context.Context, fn func(orm.DB) error) error

WithRLS @notice Runs fn in a transaction whose Postgres session variables carry the caller. See authz.WithRLS for the four ways an RLS deployment breaks silently.

type AuthLevel

type AuthLevel = authz.AuthLevel

AuthLevel @notice The @auth directive's requires argument. See authz.AuthLevel.

type Config

type Config struct {
	// DB @notice The pool everything runs on. Required.
	DB *pg.DB

	// BaseURL @notice The origin every emailed link is built under. Required.
	//
	// @dev Cannot be derived from the request: a link origin taken from the Host header is
	// Host-header injection, and a password-reset email is the last place to accept
	// attacker-controlled input. Must be https outside loopback.
	BaseURL string

	// Mailer @notice Delivers verification, reset and invite messages. Required.
	//
	// @dev No default, and deliberately no silent no-op: "I forgot to configure email" must
	// fail at construction, not at 3am when nobody can reset a password. [LogMailer] is the
	// development answer, and its name says not to ship it.
	Mailer Mailer

	// TableSchema @notice Postgres schema holding the auth_* tables. Empty means search_path.
	TableSchema string

	// IdleTimeout @notice Session inactivity timeout. Default 12h.
	IdleTimeout time.Duration

	// AbsoluteTimeout @notice Hard session lifetime, never extended. Default 14d.
	AbsoluteTimeout time.Duration

	// CookieName @notice The session cookie. Default "__Host-kal_session".
	//
	// @dev Change it only to run two kal instances on one origin. Keep the __Host- prefix: it
	// is what stops a sibling subdomain overwriting the cookie.
	CookieName string

	// Argon2 @notice Password hashing parameters. Zero fields take the OWASP defaults.
	Argon2 authn.Params

	// MaxConcurrentHashes @notice In-flight Argon2 ceiling. Zero means GOMAXPROCS.
	//
	// @dev Each hash holds ~19 MiB, so this is what stops concurrent logins from becoming a
	// remote OOM. Per replica: behind N replicas the real ceiling is N times this.
	MaxConcurrentHashes int64

	// BypassRole @notice A role for which [Scope] is a no-op and any @auth roles requirement
	// is satisfied. Empty means none — there is no implicit "admin".
	BypassRole string

	// MFAWindow @notice How recently MFA must have been satisfied for @auth(mfa: true).
	// Default 15m.
	MFAWindow time.Duration

	// AllowUnverifiedLogin @notice Lets accounts log in before verifying their email. Off by
	// default.
	AllowUnverifiedLogin bool

	// ClientIP @notice How to attribute a request to a client address. Default: the host part
	// of RemoteAddr.
	//
	// @dev Not X-Forwarded-For by default — that header is client-supplied unless a trusted
	// proxy overwrites it, and a spoofable address turns per-IP rate limiting into a bypass.
	ClientIP func(*http.Request) string

	// AllowIntrospection @notice Decides per request whether introspection is answered. Nil
	// means never.
	//
	// @dev luima turns introspection on and, since 0.2.0, offers Config.DisableIntrospection to
	// turn it off again — an all-or-nothing deploy-time switch. This is the per-request form, so
	// it can be role-gated: func(ctx) bool { return authz.HasRole(ctx, "admin") }. Off by
	// default, because the zero Config is the production posture.
	AllowIntrospection func(context.Context) bool

	// SensitiveFields @notice Fields that may be selected at most once per document. Nil takes
	// kal's defaults (login, register, the recovery mutations).
	//
	// @dev Set this if your login mutation has another name, or the aliasing guard protects
	// nothing.
	SensitiveFields []string

	// MaxAliases @notice Selections allowed per document. Zero means 100. Negative disables.
	MaxAliases int

	// MaxDepth @notice Nesting allowed per document. Zero means 15. Negative disables.
	MaxDepth int

	// JWTIssuer @notice The iss claim for the optional JWT leg. Empty disables it.
	JWTIssuer string

	// JWTKeys @notice Ed25519 signing keys, newest first. Required when JWTIssuer is set.
	//
	// @dev Two keys make rotation a deploy rather than an outage: the first signs, all verify.
	JWTKeys []ed25519.PrivateKey

	// ZK @notice Optional zero-knowledge MFA, membership login and proven claims. Nil keeps
	// today's authentication and dependency behavior at runtime.
	ZK *ZKConfig
}

Config @notice Assembles kal.

@dev The zero value is the good *production* configuration, and there is no development mode that weakens a security property. This is a deliberate inversion of luima's invariant, where a zero Config is the good development configuration with the playground and introspection on. For an auth library that polarity is wrong: a Dev bool that relaxes a cookie attribute or skips a check is a vulnerability shipped as a convenience, and it reaches production, because that is what environment flags do. Anything a developer needs is an ordinary field with an obvious name.

type Error

type Error = kalerr.Error

Error @notice A client-visible auth error with a stable code. See kalerr.Error.

type LogMailer

type LogMailer = authn.LogMailer

LogMailer @notice A development Mailer that logs messages. See authn.LogMailer.

type Mailer

type Mailer = authn.Mailer

Mailer @notice Delivers kal's transactional messages. See authn.Mailer.

type Message

type Message = authn.Message

Message @notice What to send. See authn.Message.

type Params

type Params = authn.Params

Params @notice Argon2id cost parameters. See authn.Params.

type Principal

type Principal = authz.Principal

Principal @notice The authenticated caller. See authz.Principal.

func From

func From(ctx context.Context) (*Principal, bool)

From @notice Returns the caller, and whether there is one. See authz.From.

func Require

func Require(ctx context.Context) (*Principal, error)

Require @notice Returns the caller, or a typed UNAUTHENTICATED error. See authz.Require.

type SessionInfo

type SessionInfo = session.Info

SessionInfo @notice One live session, as shown to its owner. See session.Info.

type ZKCircuit added in v0.2.0

type ZKCircuit = zkauthn.Circuit

type ZKClaim added in v0.2.0

type ZKClaim = zkauthn.Claim

type ZKClaimKind added in v0.2.0

type ZKClaimKind = zkauthn.ClaimKind

type ZKClaims added in v0.2.0

type ZKClaims = zkauthz.Claims

func NewZKClaims added in v0.2.0

func NewZKClaims(schema string) (*ZKClaims, error)

type ZKConfig added in v0.2.0

type ZKConfig struct {
	KnowledgeVerifyingKey        io.Reader
	KnowledgeVerifyingKeySHA256  []byte
	MembershipVerifyingKey       io.Reader
	MembershipVerifyingKeySHA256 []byte
	RootGrace                    time.Duration
	MaxConcurrentVerifications   int64
}

ZKConfig @notice Enables Groth16 knowledge and anonymous-membership authentication.

@dev The operator supplies both verifying keys from read-only storage and pins their hashes in application source. Proving keys are never loaded by the server or committed with kal.

type ZKCredential added in v0.2.0

type ZKCredential = zkauthn.Credential

type ZKField added in v0.2.0

type ZKField = zkauthn.Field

ZKField @notice A canonical BN254 scalar encoding. See zkauthn.Field.

func NewZKAudience added in v0.2.0

func NewZKAudience(deployment, policy, epoch string) ZKField

func ZKChallengeField added in v0.2.0

func ZKChallengeField(token string) (ZKField, error)

func ZKKnowledgeCommitment added in v0.2.0

func ZKKnowledgeCommitment(secret ZKSecret) (ZKField, error)

func ZKMembershipCommitment added in v0.2.0

func ZKMembershipCommitment(secret ZKSecret, attribute uint64) (ZKField, error)

func ZKNullifier added in v0.2.0

func ZKNullifier(secret ZKSecret, audience ZKField) (ZKField, error)

type ZKKnowledgeCircuit added in v0.2.0

type ZKKnowledgeCircuit = zkauthn.KnowledgeCircuit

type ZKKnowledgeRequest added in v0.2.0

type ZKKnowledgeRequest = zkauthn.KnowledgeRequest

type ZKKnowledgeWitness added in v0.2.0

type ZKKnowledgeWitness = zkauthn.KnowledgeWitness

type ZKMembershipCircuit added in v0.2.0

type ZKMembershipCircuit = zkauthn.MembershipCircuit

type ZKMembershipPublic added in v0.2.0

type ZKMembershipPublic = zkauthn.MembershipPublic

type ZKMembershipRequest added in v0.2.0

type ZKMembershipRequest = zkauthn.MembershipRequest

type ZKMembershipWitness added in v0.2.0

type ZKMembershipWitness = zkauthn.MembershipWitness

func ZKMembershipWitnessFor added in v0.2.0

func ZKMembershipWitnessFor(credential ZKCredential, path ZKMerklePath, claim ZKClaim,
	nullifier, challenge ZKField) ZKMembershipWitness

type ZKMerklePath added in v0.2.0

type ZKMerklePath = zkauthn.MerklePath

func ZKSingleLeafPath added in v0.2.0

func ZKSingleLeafPath(commitment ZKField) (ZKMerklePath, error)

type ZKOptions added in v0.2.0

type ZKOptions = zkauthn.Options

type ZKProvingKey added in v0.2.0

type ZKProvingKey = zkauthn.ProvingKey

func LoadZKProvingKey added in v0.2.0

func LoadZKProvingKey(kind ZKCircuit, r io.Reader) (*ZKProvingKey, error)

type ZKSecret added in v0.2.0

type ZKSecret = zkauthn.Secret

ZKSecret @notice A generated 31-byte proof secret. See zkauthn.Secret.

type ZKService added in v0.2.0

type ZKService = zkauthn.ZK

func NewZK added in v0.2.0

func NewZK(opts ZKOptions) (*ZKService, error)

type ZKVerifiedClaim added in v0.2.0

type ZKVerifiedClaim = zkauthn.VerifiedClaim

type ZKVerifyingKey added in v0.2.0

type ZKVerifyingKey = zkauthn.VerifyingKey

func LoadZKVerifyingKey added in v0.2.0

func LoadZKVerifyingKey(kind ZKCircuit, r io.Reader, wantSHA256 []byte) (*ZKVerifyingKey, error)

Directories

Path Synopsis
Package authn @notice Proving who a caller is.
Package authn @notice Proving who a caller is.
Package authz @notice Who the caller is: the Principal, carried in the request context.
Package authz @notice Who the caller is: the Principal, carried in the request context.
Package kalerr @notice kal's error contract: a client-visible auth error with a stable machine-readable code, and the presenter that puts it on the wire.
Package kalerr @notice kal's error contract: a client-visible auth error with a stable machine-readable code, and the presenter that puts it on the wire.
Package migrations @notice The auth schema, as plain SQL behind an embed.FS.
Package migrations @notice The auth schema, as plain SQL behind an embed.FS.
Package session @notice Opaque server-side sessions in Postgres — kal's primary credential.
Package session @notice Opaque server-side sessions in Postgres — kal's primary credential.
Package zkauthn @notice Groth16 authentication and anonymous authorization over BN254.
Package zkauthn @notice Groth16 authentication and anonymous authorization over BN254.
Package zkauthz @notice Request-local authorization for claims verified by zkauthn.
Package zkauthz @notice Request-local authorization for claims verified by zkauthn.

Jump to

Keyboard shortcuts

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