Documentation
¶
Overview ¶
Package postgres は authengine.Store の PostgreSQL Adapter である。
pgx/v5 の database/sql ドライバー(stdlib)を使い、スキーマは埋め込んだ SQL マイグレーションを goose ライブラリで適用する。goose CLI は不要。
原子性の保証(docs/adr/0005-storage-port.md):
- 複合操作は 1 つの DB トランザクション(READ COMMITTED)で行い、前提となる行を SELECT ... FOR UPDATE でロックしてから検査・更新する。SQLite Adapter と違いプロセス内の Mutex は使わず、複数プロセス・複数接続の競合も DB の行ロックで直列化する。
- 状態遷移は UPDATE ... WHERE status = <from> の更新行数で判定する(CAS)。
- 単一行の作成は INSERT ... ON CONFLICT DO NOTHING の更新行数で重複を判定する。
時刻は UTC の Unix ナノ秒(BIGINT)で保存し、配列・マップは JSONB で保存する。 SQLite Adapter と列の意味を揃えてあるので、両者を読み比べると方言の差だけが見える。
Index ¶
- func Migrate(ctx context.Context, db *sql.DB) error
- type Store
- func (s *Store) Close() error
- func (s *Store) ConsumeAuthorizationCode(ctx context.Context, codeHash string, now time.Time) (*authorization.AuthorizationCode, error)
- func (s *Store) CreateAuthorizationCode(ctx context.Context, code *authorization.AuthorizationCode) error
- func (s *Store) CreateClient(ctx context.Context, c *client.Client) error
- func (s *Store) CreateTransaction(ctx context.Context, txn *authorization.Transaction) error
- func (s *Store) DB() *sql.DB
- func (s *Store) FindGrant(ctx context.Context, clientID, subject string) (*grant.Grant, error)
- func (s *Store) GetClient(ctx context.Context, id string) (*client.Client, error)
- func (s *Store) GetGrant(ctx context.Context, id string) (*grant.Grant, error)
- func (s *Store) GetTokenFamily(ctx context.Context, id string) (*token.Family, error)
- func (s *Store) GetTransaction(ctx context.Context, id string) (*authorization.Transaction, error)
- func (s *Store) IssueTokens(ctx context.Context, family *token.Family, access *token.AccessToken, ...) error
- func (s *Store) ListGrantsBySubject(ctx context.Context, subject string) ([]*grant.Grant, error)
- func (s *Store) LookupAccessToken(ctx context.Context, hash string) (*token.AccessToken, *grant.Grant, error)
- func (s *Store) LookupRefreshToken(ctx context.Context, hash string) (*token.RefreshToken, error)
- func (s *Store) Purge(ctx context.Context, now time.Time) error
- func (s *Store) RevokeAccessToken(ctx context.Context, hash string, now time.Time) error
- func (s *Store) RevokeGrant(ctx context.Context, id string, now time.Time) error
- func (s *Store) RevokeTokenFamily(ctx context.Context, familyID string, now time.Time) error
- func (s *Store) RotateRefreshToken(ctx context.Context, oldHash string, next *token.RefreshToken, ...) error
- func (s *Store) TransitionTransaction(ctx context.Context, id string, from, to authorization.Status, ...) error
- func (s *Store) UpsertGrant(ctx context.Context, candidate *grant.Grant) (*grant.Grant, error)
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store は PostgreSQL 上の authengine.Store 実装。
func New ¶
New は利用者が用意した *sql.DB から Store を作る。スキーマは Migrate で適用済みであること。
Example ¶
ExampleNew は接続プールの設定を利用者が持つ場合に、用意した *sql.DB から Store を作る。 New はマイグレーションを適用しないので、先に Migrate を呼ぶ(デプロイ時のジョブで 1 度だけ流してもよい)。
package main
import (
"context"
"database/sql"
"log"
"os"
"time"
_ "github.com/jackc/pgx/v5/stdlib"
authengine "github.com/u-masato/auth-engine"
"github.com/u-masato/auth-engine/adapters/postgres"
)
// database/sql の "pgx" ドライバー
func main() {
ctx := context.Background()
db, err := sql.Open("pgx", os.Getenv("DATABASE_URL"))
if err != nil {
log.Fatal(err)
}
defer db.Close()
db.SetMaxOpenConns(20)
db.SetConnMaxIdleTime(5 * time.Minute)
if err := postgres.Migrate(ctx, db); err != nil {
log.Fatal(err)
}
store := postgres.New(db)
var _ authengine.Store = store
}
Output:
func Open ¶
Open は DSN(postgres://user:pass@host/db?sslmode=... または key=value 形式)で接続し、 マイグレーションを適用して Store を返す。
Example ¶
ExampleOpen は DSN から接続し、マイグレーションを適用した Store を作る。 複数プロセスで同じ DB を共有してよい(競合は行ロックで直列化する)。
package main
import (
"context"
"log"
"os"
_ "github.com/jackc/pgx/v5/stdlib"
authengine "github.com/u-masato/auth-engine"
"github.com/u-masato/auth-engine/adapters/postgres"
)
func main() {
ctx := context.Background()
store, err := postgres.Open(ctx, os.Getenv("DATABASE_URL")) // 例: postgres://auth:secret@db:5432/auth?sslmode=verify-full
if err != nil {
log.Fatal(err)
}
defer store.Close()
var _ authengine.Store = store // Config.Store にそのまま渡せる
}
Output:
func (*Store) ConsumeAuthorizationCode ¶
func (s *Store) ConsumeAuthorizationCode(ctx context.Context, codeHash string, now time.Time) (*authorization.AuthorizationCode, error)
ConsumeAuthorizationCode は 1 文の UPDATE ... RETURNING で原子的に消費する。 0 行なら SELECT で「消費済み」と「不明・期限切れ」を区別する。
func (*Store) CreateAuthorizationCode ¶
func (s *Store) CreateAuthorizationCode(ctx context.Context, code *authorization.AuthorizationCode) error
func (*Store) CreateClient ¶
func (*Store) CreateTransaction ¶
func (s *Store) CreateTransaction(ctx context.Context, txn *authorization.Transaction) error
func (*Store) GetTokenFamily ¶
func (*Store) GetTransaction ¶
func (s *Store) GetTransaction(ctx context.Context, id string) (*authorization.Transaction, error)
func (*Store) IssueTokens ¶
func (s *Store) IssueTokens(ctx context.Context, family *token.Family, access *token.AccessToken, refresh *token.RefreshToken) error
IssueTokens は Grant 行を FOR UPDATE でロックして active を確認してから挿入する。 同時に走る RevokeGrant とはこの行ロックで直列化される。
func (*Store) ListGrantsBySubject ¶
func (*Store) LookupAccessToken ¶
func (*Store) LookupRefreshToken ¶
func (*Store) RevokeAccessToken ¶
func (*Store) RevokeGrant ¶
RevokeGrant は Grant 行を CAS で失効させ、同じトランザクションで配下の Family・全トークンを失効させる。 Grant 行の更新が行ロックになるので、同時に走る IssueTokens(FOR UPDATE)はこの後に順番が来て ErrGrantRevoked になるか、先に挿入を終えていてここで巻き込まれるかのどちらかになる。
func (*Store) RevokeTokenFamily ¶
func (*Store) RotateRefreshToken ¶
func (s *Store) RotateRefreshToken(ctx context.Context, oldHash string, next *token.RefreshToken, access *token.AccessToken, now time.Time) error
RotateRefreshToken は旧トークン行を FOR UPDATE でロックし、active のときだけ rotated にして 後継と Access Token を挿入する。同じトークンの同時提示は行ロックで直列化され、2 番目以降は rotated を見て ErrTokenReused になる。
func (*Store) TransitionTransaction ¶
func (s *Store) TransitionTransaction(ctx context.Context, id string, from, to authorization.Status, patch authorization.Patch) error
TransitionTransaction は CAS。UPDATE の WHERE に from を含め、更新行数で成否を判定する。
func (*Store) UpsertGrant ¶
UpsertGrant は (client_id, subject) の既存行を FOR UPDATE でロックしてから grant.Merge の結果で 更新する。なければ INSERT する。2 つの要求が同時に同じ (client_id, subject) を初めて作ろうとすると 片方が unique_violation になるので、その場合はやり直して既存行への更新にする。