CrydenSync
Guide

Code Examples

A runnable example for every feature Cryden ships, using the public package facade.

The examples below use the public package facade of Cryden. Every call takes a context and an engine built by cryden.New. The engine ships no email, magic-link, webhook, or geolocation implementation, so a few of these examples define minimal host implementations of the relevant notify and security interfaces. Imports are shown in full in the quickstart and are representative for the rest.

Quickstart with the in-memory store

package main

import (
	"context"
	"os"

	"github.com/crydensync/cryden/v2"
	"github.com/crydensync/cryden/v2/store/memory"
)

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

	engine, err := cryden.New(cryden.Config{
		JWTSecret: os.Getenv("JWT_SECRET"),
		Users:     memory.NewUserStore(),
		Sessions:  memory.NewSessionStore(),
		Audit:     memory.NewAuditStore(),
	})
	if err != nil {
		panic(err)
	}

	user, err := cryden.SignUp(ctx, engine, "proguy@example.com", "Pass@2026", "1.2.3.4")
	if err != nil {
		panic(err)
	}

	tokens, err := cryden.Login(ctx, engine, "proguy@example.com", "Pass@2026", "1.2.3.4", "some-user-agent")
	if err != nil {
		panic(err)
	}

	userID, err := cryden.VerifyToken(engine, tokens.AccessToken)
	_ = user.ID
	_ = userID
}

Postgres

import (
	"database/sql"
	"os"

	_ "github.com/lib/pq"
	"github.com/crydensync/cryden/v2"
	"github.com/crydensync/cryden/v2/store/postgres"
)

func newPostgresEngine(ctx context.Context) (*cryden.Engine, error) {
	db, err := sql.Open("postgres", os.Getenv("DATABASE_URL"))
	if err != nil {
		return nil, err
	}

	return cryden.New(cryden.Config{
		JWTSecret: os.Getenv("JWT_SECRET"),
		Users:     postgres.NewUserStore(db),
		Sessions:  postgres.NewSessionStore(db),
		Audit:     postgres.NewAuditStore(db),
	})
}

Run the migrations in store/postgres/migrations in order (0001 through 0007) before use.

SQLite

import (
	"database/sql"
	"os"

	_ "modernc.org/sqlite"
	"github.com/crydensync/cryden/v2"
	"github.com/crydensync/cryden/v2/store/sqlite"
)

func newSQLiteEngine(ctx context.Context) (*cryden.Engine, error) {
	db, err := sql.Open("sqlite", "file:auth.db?_pragma=foreign_keys(1)&_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)")
	if err != nil {
		return nil, err
	}
	if err := sqlite.Migrate(ctx, db); err != nil {
		return nil, err
	}
	if err := sqlite.CheckPragmas(ctx, db); err != nil {
		return nil, err
	}

	return cryden.New(cryden.Config{
		JWTSecret: os.Getenv("JWT_SECRET"),
		Users:     sqlite.NewUserStore(db),
		Sessions:  sqlite.NewSessionStore(db),
		Audit:     sqlite.NewAuditStore(db),
	})
}

Refresh tokens and reading claims

tokens, err := cryden.RefreshToken(ctx, engine, tokens.RefreshToken)
if err != nil {
	panic(err)
}

userID, claims, err := cryden.VerifyTokenWithClaims(engine, tokens.AccessToken)
_ = userID
_ = claims

Sessions

userID := "user-123"

sessions, err := cryden.ListSessions(ctx, engine, userID)
public, err := cryden.ListPublicSessions(ctx, engine, userID)
named, err := cryden.ListNamedSessions(ctx, engine, userID)
for _, s := range named {
	fmt.Println(s.Label)
}

err = cryden.RevokeSession(ctx, engine, sessions[0].ID, userID)
err = cryden.Logout(ctx, engine, sessions[0].ID, userID)
err = cryden.LogoutAll(ctx, engine, userID)

Named sessions with a geolocator

type geolocator struct{}

func (geolocator) Locate(ctx context.Context, ip string) (security.Location, error) {
	return security.Location{City: "San Francisco", Region: "CA"}, nil
}

cfg := cryden.Config{
	JWTSecret:  os.Getenv("JWT_SECRET"),
	Users:      memory.NewUserStore(),
	Sessions:   memory.NewSessionStore(),
	Audit:      memory.NewAuditStore(),
	Geolocator: geolocator{},
}
engine, err := cryden.New(cfg)

Account management

err := cryden.ChangePassword(ctx, engine, userID, "Pass@2026", "New-Pass@2026")
err = cryden.DeleteAccount(ctx, engine, userID, "New-Pass@2026")
user, err := cryden.GetUser(ctx, engine, "proguy@example.com")

Email change

type emailSender struct{}

func (emailSender) SendVerification(ctx context.Context, to, rawToken string) error {
	link := "https://yourapp.com/verify?token=" + url.QueryEscape(rawToken)
	fmt.Printf("email to %s: %s\n", to, link)
	return nil
}

engine, err := cryden.New(cryden.Config{
	JWTSecret:     os.Getenv("JWT_SECRET"),
	Users:         memory.NewUserStore(),
	Sessions:      memory.NewSessionStore(),
	Audit:         memory.NewAuditStore(),
	Verifications: memory.NewVerificationStore(),
	EmailSender:   emailSender{},
})

err = cryden.RequestEmailChange(ctx, engine, userID, "new@example.com")
err = cryden.ConfirmEmailChange(ctx, engine, rawTokenFromTheLink)

OAuth login and linking

tokens, err := cryden.LoginWithOAuth(ctx, engine, "google", externalID, email, ip, agent)
// doubles as signup when no account matches yet

var conflict *auth.ErrOAuthEmailConflict
if errors.As(err, &conflict) {
	// the email matches an existing password account that is not linked yet;
	// have the user log in with the password, then link explicitly
	if err := cryden.LinkOAuthIdentity(ctx, engine, userID, "google", externalID, email, ip); err != nil {
		panic(err)
	}
}

TOTP enrollment

otpauthURL, err := cryden.EnrollTOTP(ctx, engine, userID)
// render otpauthURL as a QR code for the user to scan

err = cryden.ConfirmTOTP(ctx, engine, userID, codeFromTheApp)
// only after ConfirmTOTP succeeds does the account require a code

tokens, err := cryden.Login(ctx, engine, email, password, ip, agent)
var secondFactor *auth.ErrSecondFactorRequired
if errors.As(err, &secondFactor) {
	fmt.Println("enrolled methods:", secondFactor.Methods)
	tokens, err = cryden.CompleteLoginWithTOTP(ctx, engine, secondFactor.PendingToken, codeFromTheApp, ip, agent)
}

err = cryden.DisableTOTP(ctx, engine, userID, password)

Passkeys (WebAuthn)

optionsJSON, ceremonyToken, err := cryden.BeginRegisterPasskey(ctx, engine, userID)
// forward optionsJSON to navigator.credentials.create()

err = cryden.FinishRegisterPasskey(ctx, engine, userID, ceremonyToken, clientResponseJSON, "MacBook Touch ID")

passkeys, err := cryden.ListPasskeys(ctx, engine, userID)
err = cryden.DeletePasskey(ctx, engine, userID, passkeys[0].CredentialID, password)

Sign in with a passkey as the second factor:

assertionJSON, ceremonyToken, err := cryden.BeginWebAuthnLogin(ctx, engine, secondFactor.PendingToken)
// forward assertionJSON to navigator.credentials.get()

tokens, err = cryden.CompleteLoginWithWebAuthn(ctx, engine, secondFactor.PendingToken, ceremonyToken, clientResponseJSON, ip, agent)
type magicLinkSender struct{}

func (magicLinkSender) SendMagicLink(ctx context.Context, to, rawToken string) error {
	link := "https://yourapp.com/login?token=" + url.QueryEscape(rawToken)
	fmt.Printf("magic link for %s: %s\n", to, link)
	return nil
}

engine, err := cryden.New(cryden.Config{
	JWTSecret:       os.Getenv("JWT_SECRET"),
	Users:           memory.NewUserStore(),
	Sessions:        memory.NewSessionStore(),
	Audit:           memory.NewAuditStore(),
	Verifications:   memory.NewVerificationStore(),
	MagicLinkSender: magicLinkSender{},
})

err = cryden.RequestMagicLink(ctx, engine, "proguy@example.com", ip)
// nil even when the email does not exist, to avoid leaking registrations

tokens, err = cryden.CompleteMagicLink(ctx, engine, rawTokenFromTheLink, ip, agent)

Recovery codes

codes, err := cryden.GenerateRecoveryCodes(ctx, engine, userID)
// show the codes exactly once; only their hashes are stored

tokens, err = cryden.CompleteLoginWithRecoveryCode(ctx, engine, secondFactor.PendingToken, codes[0], ip, agent)

API keys

rawKey, key, err := cryden.GenerateAPIKey(ctx, engine, userID, "CI deploy bot", []string{"read", "deploy"}, 0)
// ttl 0 means the key never expires; show rawKey exactly once

identity, err := cryden.AuthenticateAPIKey(ctx, engine, rawKey)
if identity.HasScope("deploy") {
	// allow the machine to deploy
}

keys, err := cryden.ListAPIKeys(ctx, engine, userID)
err = cryden.RevokeAPIKey(ctx, engine, userID, key.ID)

Custom access token claims

import "github.com/crydensync/cryden/v2/token"

cfg.AccessTokenClaims = token.ClaimsFunc(func(ctx context.Context, userID string) (map[string]any, error) {
	return map[string]any{"role": "admin", "tenant_id": "acme"}, nil
})

userID, claims, err := cryden.VerifyTokenWithClaims(engine, tokens.AccessToken)
fmt.Println(claims["role"])

Webhooks

type webhookSender struct{}

func (webhookSender) SendWebhook(ctx context.Context, event notify.WebhookEvent) error {
	fmt.Printf("event %s: type=%s user=%s\n", event.ID, event.Type, event.UserID)
	return nil
}

cfg.Webhooks = webhookSender{}
cfg.WebhookEvents = append(cryden.DefaultWebhookEvents(), store.EventLoginSuccess)

Weekly digest

text, err := cryden.WeeklyDigest(ctx, engine)
fmt.Println(text)

since := time.Now().Add(-24 * time.Hour)
text, err = cryden.DigestSince(ctx, engine, since)
fmt.Println(text)

Support-ticket assistant

text, err := cryden.DiagnoseLoginIssue(ctx, engine, "proguy@example.com")
fmt.Println(text)

Config tuning advisor

text, err := cryden.ConfigTuningReport(ctx, engine)
fmt.Println(text)

since = time.Now().Add(-7 * 24 * time.Hour)
text, err = cryden.TuningReportSince(ctx, engine, since)
fmt.Println(text)

Ask-AI widget

import "github.com/crydensync/cryden/v2/ai"
import "github.com/crydensync/cryden/v2/widget"

answer, err := widget.Ask(ctx, widget.Config{
	Provider: myLLMProvider,
	Store:    myQueryableStore,
}, currentUserID, "what sessions do I have open?")
fmt.Println(answer.Text)

myLLMProvider implements ai.LLMProvider, which parses natural language into an ai.QueryIntent. myQueryableStore implements ai.QueryableStore against a read-only database role. currentUserID must come from the host's own authentication, never from the question; every parsed query is force-scoped to it before validation, so a prompt injection cannot make the query read another user's rows.

Advanced configuration

import (
	"github.com/redis/go-redis/v9"
	"github.com/crydensync/cryden/v2/security"
)

hasher, err := security.NewArgon2idHasher(security.DefaultArgon2idParams)

redisClient := redis.NewClient(&redis.Options{Addr: os.Getenv("REDIS_ADDR")})
limiter, err := security.NewRedisRateLimiter(redisClient, 100, time.Minute)

engine, err := cryden.New(cryden.Config{
	JWTSecret:      os.Getenv("JWT_SECRET"),
	Users:          postgres.NewUserStore(db),
	Sessions:       postgres.NewSessionStore(db),
	Audit:          postgres.NewAuditStore(db),
	Hasher:         hasher,
	RateLimiter:    limiter,
	Anomalies:      postgres.NewAnomalyStore(db),
	PasswordPolicy: security.PasswordPolicy{MinLength: 12, RequireUppercase: true},
})

The zero-valued PasswordPolicy applies security.DefaultPasswordPolicy, and the same convention applies to AnomalyThresholds and CredentialStuffingThresholds. Detection features are report-only and never block a login.

Logging toolkit

import "github.com/crydensync/cryden/v2/logger"

cfg.Logger = logger.NewMultiLogger(
	logger.NewConsoleJSONLogger(),
	logger.NewLevelFilter(
		logger.NewMaskingRedactor(vendorSink),
		logger.LevelInfo,
	),
)

Full detail stays on stdout while a redacted, level-filtered copy leaves the infrastructure.