Documentation
¶
Overview ¶
Package dynamo stores single-use authentication ceremony state in DynamoDB.
It exists so a deployment with no relational database can run the passkey, OAuth, and OIDC ceremonies. Importing it registers the ceremony table; the client itself belongs to database/dynamo, which this package reads from the request context:
import _ "github.com/shibukawa/popcornweb/database/dynamo" import _ "github.com/shibukawa/popcornweb/authstate/dynamo"
Nothing here sweeps expired records. Take decides expiry from the stored deadline, so correctness never waits for a deletion; removing the bytes is DynamoDB TTL on the expires_at attribute, which a deployment enables on the table it owns. The sqlite adapter publishes Prune because a bounded DELETE is cheap there. Here the equivalent is a Scan over every ceremony record ever written, which costs more than the records it would remove.
Index ¶
Constants ¶
const DeclaredTable = "popcornweb_authstate"
DeclaredTable is the name source uses. A deployment maps it onto its own through middleware.dynamo, like any other table.
Variables ¶
This section is empty.
Functions ¶
func NewStore ¶
NewStore builds a typed store over NewRawStore, for a caller that owns a codec and wants the ordinary contract.
func Table ¶
func Table(name string) dynamodb.TableDefinition
Table is the definition of the ceremony table. It is handwritten rather than generated: this package is the only reader and writer of the item, so the drift a generated codec closes cannot occur.
The partition key holds the namespace and the correlation key joined, rather than the namespace alone with the key as a sort key. Every ceremony of one protocol shares a namespace, so that shape would put a login spike on a single partition. What it gives up is a cheap namespace-scoped listing, which nothing needs once there is no prune.
Types ¶
type Options ¶
type Options struct {
// Table is the declared table name. Empty means DeclaredTable.
Table string
// Namespace separates the ceremonies of one protocol from another's within
// the one table. It is required.
Namespace string
// Now is injectable for tests.
Now func() time.Time
MaxKeyBytes int
MaxValueBytes int
}
Options controls key isolation and resource bounds.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store persists expiring, single-use authentication state in DynamoDB.
It is a raw store: it works on already encoded payloads, so plugin/auth can open one for a ceremony type this package cannot name. NewStore puts the codec back on for a caller that has one.
func NewRawStore ¶
NewRawStore builds the store. It opens nothing: the client comes from the request context, installed by the database/dynamo middleware. That is why it takes no client argument, unlike the sqlite adapter which takes a pool.
func (*Store) Put ¶
Put stores a value that has never been stored under this key, or whose previous record has already expired.
The condition carries the whole guarantee in one request: the item is absent, or its stored deadline has passed. That is the sqlite adapter's rule reached without a read followed by a conditional upsert.
func (*Store) Take ¶
Take removes a value and returns it, atomically.
DeleteItem asking for the old item removes and returns in one request, so the single-use guarantee needs no read followed by a delete. This is the same shape as the sqlite adapter's DELETE RETURNING, and the reason both adapters can promise it.
Everything after the removal is validation of what came back. A malformed, expired, or undecodable record stays consumed: it was single-use, and handing it back would be worse than losing it.