Help us shape the future of Go! Take the 2026 Go Developer Survey

appleid

package
v0.40.21 Latest Latest
Warning

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

Go to latest
Published: Nov 1, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package appleid keeps the link between a Rivolt account and its Sign in with Apple identity (App Store 4.8; architect's design, DA-approved Oct 24): the identity's stable `sub`, which Apple's server-to-server notifications use to name the user, and the refresh token, kept only so deleting the account can revoke it.

The `sub` is in apple_identities; the refresh token is sealed in user_secrets ("apple.refresh_token") like the Rivian session. Neither is ever logged.

Index

Constants

View Source
const (
	EventConsentRevoked = "consent-revoked"
	EventAccountDelete  = "account-delete"
	EventEmailDisabled  = "email-disabled"
	EventEmailEnabled   = "email-enabled"
)

Event types in the `events` claim.

View Source
const LinkIntentTTL = 10 * time.Minute

LinkIntentTTL is how long a started link stays open: the time to get through Apple's sign-in page.

View Source
const MaxNotificationBody = 16 << 10

MaxNotificationBody caps the request body (a real notification is about 1 KB).

View Source
const NotificationFreshness = time.Hour

NotificationFreshness is how old a notification's `iat` may be.

View Source
const SecretName = "apple.refresh_token"

SecretName is the user_secrets entry holding Apple's refresh token for a user.

Variables

View Source
var ErrSubInUse = errors.New("appleid: this Apple identity belongs to another account")

ErrSubInUse is returned when an Apple identity is already linked to a different account.

Functions

func Hash added in v0.40.19

func Hash(raw string) []byte

Hash is the sha256 the intent table stores for an OAuth state or a session id: the callback and the sign-out hook look rows up by it, and the table never holds the raw values.

Types

type Claims

type Claims struct {
	IssuedAt time.Time
	Event    Event
}

Claims are the fields of a verified notification the handler uses.

type Event

type Event struct {
	Type string `json:"type"`
	Sub  string `json:"sub"`
}

Event is the decoded `events` claim of a notification.

type LinkIntent added in v0.40.19

type LinkIntent struct {
	UserID      uuid.UUID
	SessionHash []byte
}

LinkIntent is a consumed link request: who started it and from which session.

type Mailer

type Mailer interface {
	Send(ctx context.Context, m email.Message) error
}

Mailer sends the account-delete notice (email.Client).

type NotificationStore

type NotificationStore interface {
	UserBySub(ctx context.Context, sub string) (uuid.UUID, bool, error)
	DropToken(ctx context.Context, userID uuid.UUID) error
	MarkSeen(ctx context.Context, hash []byte) (bool, error)
	Unsee(ctx context.Context, hash []byte) error
	SetEmailUndeliverable(ctx context.Context, userID uuid.UUID, v bool) error
	DeliverableEmail(ctx context.Context, userID uuid.UUID) (string, error)
}

NotificationStore is the part of Store the endpoint uses.

type Notifier

type Notifier struct {
	Verify   Verifier
	Store    NotificationStore
	Sessions SessionRevoker
	Mail     Mailer // nil: no email is sent
	Logger   *slog.Logger
	Now      func() time.Time
}

Notifier serves the notifications endpoint. Wrap it with a body cap and a rate limit where it is mounted.

func (*Notifier) ServeHTTP

func (n *Notifier) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP handles POST /api/auth/apple/notifications.

type SessionRevoker

type SessionRevoker interface {
	RevokeAll(ctx context.Context, userID uuid.UUID) (int64, error)
}

SessionRevoker signs a user out everywhere (sessions.Store).

type Store

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

Store links accounts to Apple identities. It works on the system pool (sign-in and Apple's notifications run before or outside a user's request scope).

func New

func New(db *sql.DB, s *secrets.Store) *Store

New builds a Store.

func (*Store) ConsumeLinkIntent added in v0.40.19

func (s *Store) ConsumeLinkIntent(ctx context.Context, stateHash []byte, now time.Time) (LinkIntent, bool, error)

ConsumeLinkIntent takes the intent for stateHash, once: the row is deleted whether or not it is still good. ok is false when there is none, or it has expired.

func (*Store) CreateLinkIntent added in v0.40.19

func (s *Store) CreateLinkIntent(ctx context.Context, userID uuid.UUID, stateHash, sessionHash []byte, now time.Time) error

CreateLinkIntent records that userID, signed in with the session whose hash is sessionHash, started linking an Apple identity with the OAuth state whose hash is stateHash. A user has at most one open intent: starting again replaces the earlier one.

func (*Store) DeliverableEmail

func (s *Store) DeliverableEmail(ctx context.Context, userID uuid.UUID) (string, error)

DeliverableEmail is the account's verified address, or "" if it has none or it is marked undeliverable.

func (*Store) DropSessionLinkIntents added in v0.40.19

func (s *Store) DropSessionLinkIntents(ctx context.Context, sessionHash []byte) error

DropSessionLinkIntents drops every open intent started from the session whose hash is sessionHash (sign-out).

func (*Store) DropToken

func (s *Store) DropToken(ctx context.Context, userID uuid.UUID) error

DropToken forgets the account's Apple refresh token (consent revoked, or revoked at deletion). The link stays.

func (s *Store) Link(ctx context.Context, userID uuid.UUID, sub, refreshToken string) error

Link records that userID signs in with the Apple identity sub, and keeps refreshToken (when Apple issued one) for revocation. Linking the same account again is fine (the token is replaced); a sub already linked to another account is ErrSubInUse, and an account already linked to another sub keeps its first link.

func (*Store) Linked added in v0.40.19

func (s *Store) Linked(ctx context.Context, userID uuid.UUID) (bool, error)

Linked reports whether the account has an Apple identity linked.

func (*Store) MarkSeen

func (s *Store) MarkSeen(ctx context.Context, hash []byte) (bool, error)

MarkSeen records a notification hash and reports whether it was new.

func (*Store) PurgeLinkIntents added in v0.40.19

func (s *Store) PurgeLinkIntents(ctx context.Context) (int64, error)

PurgeLinkIntents is the retention sweep: intents created more than a day ago, spent or not.

func (*Store) PurgeSeen

func (s *Store) PurgeSeen(ctx context.Context) (int64, error)

PurgeSeen deletes replay-guard rows older than two days (retention job).

func (*Store) RefreshToken

func (s *Store) RefreshToken(ctx context.Context, userID uuid.UUID) (string, error)

RefreshToken returns the account's kept Apple refresh token, or "" when there is none.

func (*Store) SetEmailUndeliverable

func (s *Store) SetEmailUndeliverable(ctx context.Context, userID uuid.UUID, v bool) error

SetEmailUndeliverable marks the account's address as not reachable, for non-essential mail, or clears the mark. Marking only applies to an account whose address IS an Apple relay address: Apple's "forwarding off" event is about that relay, so an account with its own address (a linked account, or one that joined by sharing its email) is left alone. Clearing is always allowed.

func (s *Store) Unlink(ctx context.Context, userID uuid.UUID) error

Unlink removes the account's Apple identity and forgets the kept refresh token. The caller has revoked the token at Apple, or queued that, first: after this the token is gone from here. Unlinking an account that is not linked is not an error.

func (*Store) Unsee

func (s *Store) Unsee(ctx context.Context, hash []byte) error

Unsee forgets a notification hash (the action failed; Apple will retry).

func (*Store) UserBySub

func (s *Store) UserBySub(ctx context.Context, sub string) (uuid.UUID, bool, error)

UserBySub returns the account an Apple identity is linked to, or false.

func (*Store) UsernameBySub

func (s *Store) UsernameBySub(ctx context.Context, sub string) (string, bool, error)

UsernameBySub returns the account key (users.username) of the account an Apple identity is linked to, or false.

type Verifier

type Verifier interface {
	Verify(ctx context.Context, rawJWT string) (Claims, error)
}

Verifier checks a notification JWT's signature, issuer and audience and returns its claims.

func NewVerifier

func NewVerifier(ctx context.Context, issuer, clientID string) Verifier

NewVerifier verifies against keys at issuer+"/auth/keys" (cached, refetched on an unknown key id) for the Services ID clientID. Apple's notifications carry an `exp` that we don't rely on: freshness is the `iat` rule.

Jump to

Keyboard shortcuts

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