// Private-DB models for simplegit. // // This is a credential-centric access model: there is NO users table. The two // things that can authenticate a git operation are an SSH public key or a PAT, // and each is granted access to repos directly via the ACL table. This matches // the Store interface (cmd/listener.go): AccessBySSHKey / AccessByToken resolve // a credential and check it against a repo, with no user id in between. // // Anonymous access (no credential) is governed by Repo.IsPrivate: a public repo // allows anonymous read; write always requires a credential. That special case // lives in the store layer, not in ACL -- ACL only records explicit grants. // // xorm derives the schema from the struct tags via Sync2 (see InitEngine), so // there is no hand-written DDL. GonicMapper is required so SSHKey -> "ssh_key" // (the default SnakeMapper would produce "s_s_h_key"); this matches // simpleconsole, whose raw-SQL joins rely on the same names. package state import ( "time" "xorm.io/xorm" "xorm.io/xorm/names" ) // BaseModel is the common row shape: auto-increment PK plus xorm-managed // created/updated/deleted timestamps (soft-delete via DeletedAt). type BaseModel struct { ID int64 `xorm:"pk autoincr" json:"id"` CreatedAt time.Time `xorm:"created" json:"created_at"` UpdatedAt time.Time `xorm:"updated" json:"updated_at"` DeletedAt *time.Time `xorm:"deleted" json:"deleted_at,omitempty"` } // Repo is a registered repository. The on-disk bare repo lives under the git // root; this row is the authority for its namespace/name and visibility. type Repo struct { BaseModel `xorm:"extends"` NamespaceID int64 `xorm:"notnull unique(cred)" json:"namespace_id"` Namespace string `xorm:"varchar(255) notnull index" json:"namespace"` NameID int64 `xorm:"notnull unique(cred)" json:"name_id"` Name string `xorm:"varchar(255) notnull" json:"name"` IsPrivate bool `xorm:"default false" json:"is_private"` // public repos allow anonymous read } // SSHKey is a registered public key, resolved by fingerprint in AccessBySSHKey. type SSHKey struct { BaseModel `xorm:"extends"` PublicKey string `xorm:"text notnull" json:"-"` Fingerprint string `xorm:"varchar(255) unique notnull" json:"fingerprint"` // "SHA256:..." KeyType string `xorm:"varchar(50) notnull" json:"key_type"` // ssh-ed25519, ssh-rsa, ... Comment string `xorm:"varchar(255)" json:"comment,omitempty"` LastUsedAt *time.Time `xorm:"" json:"last_used_at,omitempty"` } // PAT is an opaque HTTP credential. Only its sha256 hash is stored; the // plaintext is returned once at creation. AccessByToken hashes the presented // token and looks it up here by token_hash. type PAT struct { BaseModel `xorm:"extends"` TokenHash string `xorm:"varchar(64) unique notnull" json:"-"` // hex(sha256(plaintext)) Prefix string `xorm:"varchar(20)" json:"prefix"` // first chars, for display only Name string `xorm:"varchar(255)" json:"name"` ExpiresAt *time.Time `xorm:"" json:"expires_at,omitempty"` LastUsedAt *time.Time `xorm:"" json:"last_used_at,omitempty"` } // ACL grants a credential access to a repo. CredType selects which table CredID // points at -- the foreign key is polymorphic by design, so it is NOT // database-enforced: deleting a credential must clean up its ACL rows in the // store layer. The unique(cred) group means one grant per credential per repo; // raise it by updating perm, not by inserting a duplicate. type ACL struct { BaseModel `xorm:"extends"` CredType int `xorm:"notnull unique(cred) index" json:"cred_type"` // CredTypePAT or CredTypeSSH CredID int64 `xorm:"notnull unique(cred)" json:"cred_id"` // pats.id or ssh_keys.id (per CredType) TargetType int `xorm:"notnull unique(cred) index" json:"target_type"` // TargetType TargetID int64 `xorm:"notnull unique(cred)" json:"target_id"` Perm string `xorm:"varchar(20) default 'read'" json:"perm"` // PermRead | PermWrite | PermAdmin } type CredType int // Credential kinds for ACL.CredType. const ( CredTypePAT CredType = 0 // HTTP transport (PAT) CredTypeSSH CredType = 1 // SSH transport (public key) ) type TargetType int const ( TargetTypeRepo TargetType = 0 TargetTypeNS TargetType = 1 ) // InitEngine opens an xorm engine on driver/dsn, applies the GonicMapper (see // package doc), and syncs all model tables. It mirrors simpleconsole's // models.InitEngine so the two services share table/column naming. func InitEngine(driverName, dataSourceName string) (*xorm.Engine, error) { engine, err := xorm.NewEngine(driverName, dataSourceName) if err != nil { return nil, err } engine.SetTableMapper(names.GonicMapper{}) engine.SetColumnMapper(names.GonicMapper{}) if err := engine.Sync2( new(Repo), new(SSHKey), new(PAT), new(ACL), ); err != nil { return nil, err } return engine, nil }