auth

package
v1.1.8 Latest Latest
Warning

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

Go to latest
Published: Jun 15, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package auth — HTTP client for nSelf auth server operations.

Package auth — device code polling loop for the CLI login flow.

Package auth provides CLI authentication storage and client utilities. ~/.nself/auth.json is the credential store with 0600 permissions.

Index

Constants

View Source
const (
	// DefaultRotationWindowDays is the maximum age of a JWT key before
	// nself doctor --deep reports a warning. Override with
	// NSELF_JWT_ROTATION_WINDOW_DAYS.
	DefaultRotationWindowDays = 90

	// DefaultRotationHardDays is the age at which JWT-ROT-01 escalates from
	// warn to fail. Defaults to 2× the rotation window. Override with
	// NSELF_JWT_ROTATION_HARD_DAYS.
	DefaultRotationHardMultiplier = 2

	// DefaultRotationLogPath is where rotation events are recorded.
	// Falls back to XDG_STATE_HOME (or ~/.local/state) when not writable.
	// Override with NSELF_JWT_ROTATION_LOG.
	DefaultRotationLogPath = "/var/lib/nself/jwt-rotation.log"

	// GracePeriodHours is the window during which the old and new keys are
	// both accepted. This allows running tokens signed by the old key to
	// expire naturally without invalidating active sessions.
	GracePeriodHours = 12
)

Variables

View Source
var ErrNotLoggedIn = errors.New("not logged in — run 'nself login' to authenticate")

ErrNotLoggedIn is returned when no auth.json exists or token is missing.

View Source
var ErrPollTimeout = fmt.Errorf("authorization timed out — the login URL has expired. Run 'nself login' to try again")

ErrPollTimeout is returned when the device code polling window expires.

Functions

func ActivateLicense

func ActivateLicense(ctx context.Context, accessToken, licenseID string) error

ActivateLicense activates a license key on the current device.

func AuthServerURL

func AuthServerURL() string

AuthServerURL is the base URL of the auth server. Overridable via NSELF_AUTH_SERVER_URL for testing.

func CLIAuthBaseURL

func CLIAuthBaseURL() string

CLIAuthBaseURL is the web UI for the CLI device auth page.

func DeleteAuthFile

func DeleteAuthFile() error

DeleteAuthFile removes ~/.nself/auth.json if it exists. Returns nil if the file does not exist (idempotent).

func GenerateJWTKey added in v1.0.14

func GenerateJWTKey() (string, error)

GenerateJWTKey returns a securely-random hex-encoded key suitable for use as the HASURA_GRAPHQL_JWT_SECRET key material (HS256/HS512).

func GetAuthFilePath

func GetAuthFilePath() string

GetAuthFilePath returns the path for use in help text (never reads content).

func InviteTeamMember

func InviteTeamMember(ctx context.Context, accessToken, email string) error

InviteTeamMember sends a team invitation to the given email.

func IsLoggedIn

func IsLoggedIn() bool

IsLoggedIn returns true if ~/.nself/auth.json exists and has a token.

func LastRotationTime added in v1.0.14

func LastRotationTime(logPath string) (time.Time, error)

LastRotationTime reads the rotation log and returns the timestamp of the most recent successful rotation. Returns the zero time.Time if no rotation has ever been recorded or if the log cannot be read.

func RemoveTeamMember

func RemoveTeamMember(ctx context.Context, accessToken, email string) error

RemoveTeamMember removes a member from the account's team.

func RevokeDevice

func RevokeDevice(ctx context.Context, accessToken, deviceID string) error

RevokeDevice revokes a specific device session.

func RevokeSession

func RevokeSession(ctx context.Context, accessToken string, all bool) error

RevokeSession calls POST /auth/signout to revoke the session server-side.

func RotationHardDays added in v1.0.14

func RotationHardDays() int

RotationHardDays returns the age at which JWT-ROT-01 escalates to fail (not just warn). Defaults to 2× RotationWindowDays. Override with NSELF_JWT_ROTATION_HARD_DAYS.

func RotationLogPath added in v1.0.14

func RotationLogPath() string

RotationLogPath returns the effective rotation log path, honouring the NSELF_JWT_ROTATION_LOG environment variable override. When the primary path is not writable (e.g. /var/lib/nself/ on a user workstation), it falls back to ${XDG_STATE_HOME:-$HOME/.local/state}/nself/jwt-rotation.log.

func RotationWindowDays added in v1.0.14

func RotationWindowDays() int

RotationWindowDays returns the effective rotation window, honouring the NSELF_JWT_ROTATION_WINDOW_DAYS environment variable override. Returns an error on malformed input.

func SetTeamMemberRole

func SetTeamMemberRole(ctx context.Context, accessToken, email, role string) error

SetTeamMemberRole updates a member's role on the account's team.

func TransferLicense

func TransferLicense(ctx context.Context, accessToken, licenseID, toEmail string) error

TransferLicense transfers a license from the current account to another email.

func WriteAuthFile

func WriteAuthFile(af *AuthFile) error

WriteAuthFile writes auth credentials to ~/.nself/auth.json with 0600 perms. Creates ~/.nself/ directory with 0700 if it does not exist.

Types

type AccountInfo

type AccountInfo struct {
	Authenticated bool `json:"authenticated"`
	Account       struct {
		ID            string `json:"id"`
		Email         string `json:"email"`
		DisplayName   string `json:"display_name"`
		AvatarURL     string `json:"avatar_url"`
		Tier          string `json:"tier"`
		EmailVerified bool   `json:"email_verified"`
		MFAEnabled    bool   `json:"mfa_enabled"`
	} `json:"account"`
}

AccountInfo is the response from GET /auth/session.

func GetSession

func GetSession(ctx context.Context, accessToken string) (*AccountInfo, error)

GetSession returns the account info for the given access token.

type AuthAPIError

type AuthAPIError struct {
	Code    string `json:"error"`
	Message string `json:"message"`
	Status  int
}

AuthAPIError is returned when the auth server returns a non-2xx response.

func (*AuthAPIError) Error

func (e *AuthAPIError) Error() string

type AuthFile

type AuthFile struct {
	// SessionToken is the opaque session token for server-side session lookup.
	SessionToken string `json:"session_token"`
	// AccessToken is the RS256 JWT for subapp requests.
	AccessToken string `json:"access_token"`
	// Email is the account email (display only — not used for auth).
	Email string `json:"email"`
	// Tier is the account tier (display only).
	Tier string `json:"tier"`
	// DisplayName is the account display name (display only).
	DisplayName string `json:"display_name,omitempty"`
	// Bundles are the unlocked plugin bundles (display only).
	Bundles []string `json:"bundles,omitempty"`
	// ExpiresAt is the access token expiry as RFC3339 string.
	ExpiresAt string `json:"expires_at,omitempty"`
}

AuthFile holds the persisted auth credentials.

func ReadAuthFile

func ReadAuthFile() (*AuthFile, error)

ReadAuthFile reads and parses ~/.nself/auth.json. Returns ErrNotLoggedIn if file does not exist.

type DeviceCodeResponse

type DeviceCodeResponse struct {
	DeviceCode      string `json:"device_code"`
	UserCode        string `json:"user_code"`        // XXXX-YYYY format shown to user
	VerificationURL string `json:"verification_url"` // nself.org/auth/cli?code=...
	ExpiresInSec    int    `json:"expires_in"`
	IntervalSec     int    `json:"interval"`
}

DeviceCodeResponse is returned by the device authorization endpoint.

func DeviceAuthorize

func DeviceAuthorize(ctx context.Context) (*DeviceCodeResponse, error)

DeviceAuthorize initiates the device code flow. Returns a DeviceCodeResponse with the code to display to the user.

type DeviceEntry

type DeviceEntry struct {
	ID         string `json:"id"`
	Name       string `json:"name"`
	OS         string `json:"os"`
	LastActive string `json:"last_active"`
	IsCurrent  bool   `json:"is_current"`
}

DeviceEntry represents one registered device for an account.

func GetDevices

func GetDevices(ctx context.Context, accessToken string) ([]DeviceEntry, error)

GetDevices returns the list of registered devices for the account.

type LicenseInfo

type LicenseInfo struct {
	ID            string   `json:"id"`
	Product       string   `json:"product"`
	Tier          string   `json:"tier"`
	Bundles       []string `json:"bundles"`
	SeatsIncluded int      `json:"seats_included"`
	SeatsUsed     int      `json:"seats_used"`
	IsActive      bool     `json:"is_active"`
	ActivatedAt   string   `json:"activated_at"`
	ExpiresAt     string   `json:"expires_at"`
}

LicenseInfo represents a single license entry.

func GetLicenses

func GetLicenses(ctx context.Context, accessToken string) ([]LicenseInfo, error)

GetLicenses returns the list of active licenses for the account.

type PollResult

type PollResult struct {
	Token *TokenResponse
}

PollResult holds the outcome of a completed device code poll.

func PollDeviceCode

func PollDeviceCode(ctx context.Context, deviceCode string, onPoll func(elapsed time.Duration)) (*PollResult, error)

PollDeviceCode polls the auth server until the user authorizes the device code, the timeout expires, or the context is cancelled.

onPoll is called before each poll attempt (use for progress indicators). Pass nil to skip the callback.

Returns ErrPollTimeout if the poll window expires without user action. Returns context.Canceled/context.DeadlineExceeded if ctx is cancelled.

type RotateResult added in v1.0.14

type RotateResult struct {
	NewKey     string
	OldKey     string // raw hex key, NOT the JSON envelope
	GraceUntil time.Time
	LogEntry   string
}

RotateResult holds the outcome of a rotation attempt.

OldKey always holds the raw hex key string that was previously in use (not the JSON envelope). The env var HASURA_GRAPHQL_JWT_SECRET contains that key wrapped in a JSON envelope: {"type":"HS256","key":"<OldKey>"}. Callers that need the envelope format must construct it themselves.

func RotateJWTKey added in v1.0.14

func RotateJWTKey(currentKey string) (*RotateResult, error)

RotateJWTKey generates a new JWT key, writes a rotation log entry, and returns the result. It does NOT modify any env file — callers are responsible for writing the new key into .env.secrets and reloading Hasura.

currentKey must be the raw hex key (not the JSON envelope). If currentKey is empty, RotateJWTKey returns an error rather than silently storing an empty OldKey — an empty old key most likely indicates the caller did not read the current value before rotating.

The dual-key grace period is signalled via RotateResult.GraceUntil. During [now, GraceUntil] both the old key and the new key are valid for verifying tokens. After GraceUntil, only the new key should be used.

This function NEVER reads from or writes to production secrets files — it only generates the key material and records the event in the rotation log.

type TeamMember

type TeamMember struct {
	Name     string `json:"name"`
	Email    string `json:"email"`
	Role     string `json:"role"`
	JoinedAt string `json:"joined_at"`
}

TeamMember represents one member of an account's team.

func GetTeamMembers

func GetTeamMembers(ctx context.Context, accessToken string) ([]TeamMember, error)

GetTeamMembers returns the list of team members for the account.

type TokenResponse

type TokenResponse struct {
	AccessToken  string   `json:"access_token"`
	SessionToken string   `json:"session_token"`
	Email        string   `json:"email"`
	Tier         string   `json:"tier"`
	DisplayName  string   `json:"display_name,omitempty"`
	Bundles      []string `json:"bundles,omitempty"`
	ExpiresAt    string   `json:"expires_at"`
}

TokenResponse is returned when the device code is exchanged for a token.

func PollToken

func PollToken(ctx context.Context, deviceCode string) (*TokenResponse, error)

PollToken polls the auth server for the device code exchange result. Returns (nil, nil) if the user hasn't authorized yet (authorization_pending). Returns an error on timeout or other failure.

func RefreshToken

func RefreshToken(ctx context.Context, accessToken string) (*TokenResponse, error)

RefreshToken exchanges an existing session for a new access token.

Jump to

Keyboard shortcuts

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