Compare commits
13 Commits
e8bdbd53e0
...
37dfa6edc4
| Author | SHA1 | Date | |
|---|---|---|---|
| 37dfa6edc4 | |||
| 8b0435f84b | |||
| 205c71e1f8 | |||
| 555f3dcd34 | |||
| 7ea5dfe058 | |||
| 9a7fac0a90 | |||
| 76b33e904d | |||
| 3645cc14e0 | |||
| 72011a1221 | |||
| 54f0e75b1f | |||
| 7f3a8b24ff | |||
| d54770112f | |||
| f7d94f7aa9 |
@@ -10,15 +10,9 @@ import "time"
|
|||||||
import "codit/internal/models"
|
import "codit/internal/models"
|
||||||
import "codit/internal/util"
|
import "codit/internal/util"
|
||||||
|
|
||||||
const AuthProviderTypeDB = "db"
|
|
||||||
const AuthProviderTypeLDAP = "ldap"
|
|
||||||
const AuthProviderTypeOIDC = "oidc"
|
|
||||||
|
|
||||||
const BuiltinDBProviderPublicID = "db"
|
const BuiltinDBProviderPublicID = "db"
|
||||||
|
|
||||||
const GroupSyncModeOff string = "off"
|
|
||||||
const GroupSyncModeFirstLogin string = "first_login"
|
|
||||||
const GroupSyncModeSync string = "sync"
|
|
||||||
|
|
||||||
func scanAuthProvider(row interface {
|
func scanAuthProvider(row interface {
|
||||||
Scan(dest ...any) error
|
Scan(dest ...any) error
|
||||||
@@ -81,9 +75,9 @@ func normalizeAuthProviderGroupSyncMode(value string) (string, error) {
|
|||||||
|
|
||||||
mode = strings.TrimSpace(value)
|
mode = strings.TrimSpace(value)
|
||||||
if mode == "" {
|
if mode == "" {
|
||||||
return GroupSyncModeOff, nil
|
return models.GroupSyncModeOff, nil
|
||||||
}
|
}
|
||||||
if mode == GroupSyncModeOff || mode == GroupSyncModeFirstLogin || mode == GroupSyncModeSync {
|
if mode == models.GroupSyncModeOff || mode == models.GroupSyncModeFirstLogin || mode == models.GroupSyncModeSync {
|
||||||
return mode, nil
|
return mode, nil
|
||||||
}
|
}
|
||||||
return "", errors.New("invalid group_sync_mode")
|
return "", errors.New("invalid group_sync_mode")
|
||||||
@@ -160,7 +154,7 @@ func insertAuthProviderGroupMappings(exec sqlExecutor, providerID string, mappin
|
|||||||
FROM auth_providers p
|
FROM auth_providers p
|
||||||
JOIN user_groups g ON g.public_id = ?
|
JOIN user_groups g ON g.public_id = ?
|
||||||
WHERE p.public_id = ? AND g.scope = ?`,
|
WHERE p.public_id = ? AND g.scope = ?`,
|
||||||
claimValue, now, now, groupID, strings.TrimSpace(providerID), UserGroupScopeExplicit)
|
claimValue, now, now, groupID, strings.TrimSpace(providerID), models.UserGroupScopeExplicit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -287,10 +281,10 @@ func (s *Store) CreateAuthProvider(ctx context.Context, item models.AuthProvider
|
|||||||
item.ID, err = util.NewID()
|
item.ID, err = util.NewID()
|
||||||
if err != nil { return item, err }
|
if err != nil { return item, err }
|
||||||
}
|
}
|
||||||
if item.Type == AuthProviderTypeLDAP && strings.TrimSpace(item.LDAPUserFilter) == "" {
|
if item.Type == models.AuthProviderTypeLDAP && strings.TrimSpace(item.LDAPUserFilter) == "" {
|
||||||
item.LDAPUserFilter = "(uid={username})"
|
item.LDAPUserFilter = "(uid={username})"
|
||||||
}
|
}
|
||||||
if item.Type == AuthProviderTypeOIDC && strings.TrimSpace(item.OIDCScopes) == "" {
|
if item.Type == models.AuthProviderTypeOIDC && strings.TrimSpace(item.OIDCScopes) == "" {
|
||||||
item.OIDCScopes = "openid profile email"
|
item.OIDCScopes = "openid profile email"
|
||||||
}
|
}
|
||||||
item.GroupSyncMode, err = normalizeAuthProviderGroupSyncMode(item.GroupSyncMode)
|
item.GroupSyncMode, err = normalizeAuthProviderGroupSyncMode(item.GroupSyncMode)
|
||||||
@@ -343,10 +337,10 @@ func (s *Store) UpdateAuthProvider(ctx context.Context, item models.AuthProvider
|
|||||||
now = time.Now().Unix()
|
now = time.Now().Unix()
|
||||||
item.UpdatedAt = now
|
item.UpdatedAt = now
|
||||||
|
|
||||||
if item.Type == AuthProviderTypeLDAP && strings.TrimSpace(item.LDAPUserFilter) == "" {
|
if item.Type == models.AuthProviderTypeLDAP && strings.TrimSpace(item.LDAPUserFilter) == "" {
|
||||||
item.LDAPUserFilter = "(uid={username})"
|
item.LDAPUserFilter = "(uid={username})"
|
||||||
}
|
}
|
||||||
if item.Type == AuthProviderTypeOIDC && strings.TrimSpace(item.OIDCScopes) == "" {
|
if item.Type == models.AuthProviderTypeOIDC && strings.TrimSpace(item.OIDCScopes) == "" {
|
||||||
item.OIDCScopes = "openid profile email"
|
item.OIDCScopes = "openid profile email"
|
||||||
}
|
}
|
||||||
item.GroupSyncMode, err = normalizeAuthProviderGroupSyncMode(item.GroupSyncMode)
|
item.GroupSyncMode, err = normalizeAuthProviderGroupSyncMode(item.GroupSyncMode)
|
||||||
|
|||||||
@@ -498,9 +498,7 @@ func (s *Store) CreateBlockAttachment(ctx context.Context, boardID, blockID stri
|
|||||||
return item, err
|
return item, err
|
||||||
}
|
}
|
||||||
err = commitIfOwned(tx, owned)
|
err = commitIfOwned(tx, owned)
|
||||||
if err != nil {
|
if err != nil { return item, err }
|
||||||
return item, err
|
|
||||||
}
|
|
||||||
return item, nil
|
return item, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,16 +9,14 @@ import "time"
|
|||||||
import "codit/internal/models"
|
import "codit/internal/models"
|
||||||
import "codit/internal/util"
|
import "codit/internal/util"
|
||||||
|
|
||||||
const UserGroupScopeExplicit string = "explicit"
|
|
||||||
const UserGroupScopeAllUsers string = "all_users"
|
|
||||||
|
|
||||||
func NormalizeUserGroupScope(value string) string {
|
func NormalizeUserGroupScope(value string) string {
|
||||||
value = strings.ToLower(strings.TrimSpace(value))
|
value = strings.ToLower(strings.TrimSpace(value))
|
||||||
switch value {
|
switch value {
|
||||||
case UserGroupScopeAllUsers:
|
case models.UserGroupScopeAllUsers:
|
||||||
return UserGroupScopeAllUsers
|
return models.UserGroupScopeAllUsers
|
||||||
default:
|
default:
|
||||||
return UserGroupScopeExplicit
|
return models.UserGroupScopeExplicit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +98,7 @@ func (s *Store) UpdateUserGroup(ctx context.Context, item models.UserGroup) (mod
|
|||||||
item.Scope = NormalizeUserGroupScope(item.Scope)
|
item.Scope = NormalizeUserGroupScope(item.Scope)
|
||||||
tx, owned, err = s.beginImmediateContext(ctx)
|
tx, owned, err = s.beginImmediateContext(ctx)
|
||||||
if err != nil { return item, err }
|
if err != nil { return item, err }
|
||||||
if item.Scope != UserGroupScopeExplicit {
|
if item.Scope != models.UserGroupScopeExplicit {
|
||||||
err = tx.QueryRow(`SELECT COUNT(*)
|
err = tx.QueryRow(`SELECT COUNT(*)
|
||||||
FROM auth_provider_group_mappings m
|
FROM auth_provider_group_mappings m
|
||||||
JOIN user_groups g ON g.id = m.group_id
|
JOIN user_groups g ON g.id = m.group_id
|
||||||
@@ -157,7 +155,7 @@ func (s *Store) DeleteUserGroup(ctx context.Context, groupID string) error {
|
|||||||
// TODO: this part needs to be re-thought through. This is still user created group. If it can't be deleted, that's horrible
|
// TODO: this part needs to be re-thought through. This is still user created group. If it can't be deleted, that's horrible
|
||||||
// above all creating another user group with this scope gives the unique constraint error... so Creating a user group
|
// above all creating another user group with this scope gives the unique constraint error... so Creating a user group
|
||||||
// should translate it to human friendly message.
|
// should translate it to human friendly message.
|
||||||
if NormalizeUserGroupScope(scope) == UserGroupScopeAllUsers {
|
if NormalizeUserGroupScope(scope) == models.UserGroupScopeAllUsers {
|
||||||
rollbackIfOwned(tx, owned)
|
rollbackIfOwned(tx, owned)
|
||||||
return errors.New("all users group cannot be deleted")
|
return errors.New("all users group cannot be deleted")
|
||||||
}
|
}
|
||||||
@@ -196,7 +194,7 @@ func (s *Store) ListUserGroupMembers(groupID string) ([]models.UserGroupMember,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if NormalizeUserGroupScope(group.Scope) == UserGroupScopeAllUsers {
|
if NormalizeUserGroupScope(group.Scope) == models.UserGroupScopeAllUsers {
|
||||||
rows, err = s.Query(`SELECT ? AS group_public_id, u.public_id, u.username, u.display_name, 0 AS created_at
|
rows, err = s.Query(`SELECT ? AS group_public_id, u.public_id, u.username, u.display_name, 0 AS created_at
|
||||||
FROM users u
|
FROM users u
|
||||||
WHERE u.disabled = 0
|
WHERE u.disabled = 0
|
||||||
@@ -242,7 +240,7 @@ func (s *Store) AddUserGroupMember(ctx context.Context, groupID string, userID s
|
|||||||
rollbackIfOwned(tx, owned)
|
rollbackIfOwned(tx, owned)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if NormalizeUserGroupScope(scope) != UserGroupScopeExplicit {
|
if NormalizeUserGroupScope(scope) != models.UserGroupScopeExplicit {
|
||||||
rollbackIfOwned(tx, owned)
|
rollbackIfOwned(tx, owned)
|
||||||
return errors.New("virtual groups do not accept explicit members")
|
return errors.New("virtual groups do not accept explicit members")
|
||||||
}
|
}
|
||||||
@@ -272,7 +270,7 @@ func (s *Store) RemoveUserGroupMember(ctx context.Context, groupID string, userI
|
|||||||
rollbackIfOwned(tx, owned)
|
rollbackIfOwned(tx, owned)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if NormalizeUserGroupScope(scope) != UserGroupScopeExplicit {
|
if NormalizeUserGroupScope(scope) != models.UserGroupScopeExplicit {
|
||||||
rollbackIfOwned(tx, owned)
|
rollbackIfOwned(tx, owned)
|
||||||
return errors.New("virtual groups do not have explicit members")
|
return errors.New("virtual groups do not have explicit members")
|
||||||
}
|
}
|
||||||
@@ -433,13 +431,13 @@ func (s *Store) GetProjectRoleForPrincipal(projectID string, principalID string)
|
|||||||
func projectRoleRank(value string) int {
|
func projectRoleRank(value string) int {
|
||||||
var v string
|
var v string
|
||||||
v = strings.ToLower(strings.TrimSpace(value))
|
v = strings.ToLower(strings.TrimSpace(value))
|
||||||
if v == "admin" {
|
if v == models.RoleAdmin {
|
||||||
return 3
|
return 3
|
||||||
}
|
}
|
||||||
if v == "writer" {
|
if v == models.RoleWriter {
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
if v == "viewer" {
|
if v == models.RoleViewer {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import "time"
|
|||||||
import "codit/internal/models"
|
import "codit/internal/models"
|
||||||
import "codit/internal/util"
|
import "codit/internal/util"
|
||||||
|
|
||||||
const RPMRepoModeLocal string = "local"
|
|
||||||
const RPMRepoModeMirror string = "mirror"
|
|
||||||
|
|
||||||
func (s *Store) ListRPMRepoDirs(repoID string) ([]models.RPMRepoDir, error) {
|
func (s *Store) ListRPMRepoDirs(repoID string) ([]models.RPMRepoDir, error) {
|
||||||
var rows *sql.Rows
|
var rows *sql.Rows
|
||||||
@@ -74,7 +72,7 @@ func (s *Store) UpsertRPMRepoDir(item models.RPMRepoDir) error {
|
|||||||
item.TLSInsecureSkipVerify,
|
item.TLSInsecureSkipVerify,
|
||||||
item.SyncIntervalSec,
|
item.SyncIntervalSec,
|
||||||
item.SyncEnabled,
|
item.SyncEnabled,
|
||||||
NormalizeRPMRepoMode(item.Mode) == RPMRepoModeMirror,
|
NormalizeRPMRepoMode(item.Mode) == models.RPMRepoModeMirror,
|
||||||
int64(0),
|
int64(0),
|
||||||
now,
|
now,
|
||||||
now)
|
now)
|
||||||
@@ -524,10 +522,10 @@ func (s *Store) CleanupRPMMirrorRunsRetention(repoID string, path string, keepCo
|
|||||||
func NormalizeRPMRepoMode(mode string) string {
|
func NormalizeRPMRepoMode(mode string) string {
|
||||||
var v string
|
var v string
|
||||||
v = strings.ToLower(strings.TrimSpace(mode))
|
v = strings.ToLower(strings.TrimSpace(mode))
|
||||||
if v == RPMRepoModeMirror {
|
if v == models.RPMRepoModeMirror {
|
||||||
return RPMRepoModeMirror
|
return models.RPMRepoModeMirror
|
||||||
}
|
}
|
||||||
return RPMRepoModeLocal
|
return models.RPMRepoModeLocal
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) DeleteRPMRepoDir(repoID string, path string) error {
|
func (s *Store) DeleteRPMRepoDir(repoID string, path string) error {
|
||||||
|
|||||||
@@ -513,7 +513,7 @@ func (s *Store) CreateProject(ctx context.Context, project models.Project) (mode
|
|||||||
}
|
}
|
||||||
_, err = tx.Exec(`INSERT INTO project_role_bindings (project_id, subject_type, subject_id, role, created_at, updated_at)
|
_, err = tx.Exec(`INSERT INTO project_role_bindings (project_id, subject_type, subject_id, role, created_at, updated_at)
|
||||||
VALUES ((SELECT id FROM projects WHERE public_id = ?), 'user', (SELECT id FROM users WHERE public_id = ?), ?, ?, ?)`,
|
VALUES ((SELECT id FROM projects WHERE public_id = ?), 'user', (SELECT id FROM users WHERE public_id = ?), ?, ?, ?)`,
|
||||||
project.ID, project.CreatedBy, "admin", project.CreatedAt, project.UpdatedAt)
|
project.ID, project.CreatedBy, models.RoleAdmin, project.CreatedAt, project.UpdatedAt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rollbackIfOwned(tx, owned)
|
rollbackIfOwned(tx, owned)
|
||||||
return project, err
|
return project, err
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import "time"
|
|||||||
|
|
||||||
import "golang.org/x/crypto/acme"
|
import "golang.org/x/crypto/acme"
|
||||||
|
|
||||||
|
import "codit/internal/db"
|
||||||
import "codit/internal/models"
|
import "codit/internal/models"
|
||||||
|
|
||||||
type acmeProfileRequest struct {
|
type acmeProfileRequest struct {
|
||||||
@@ -466,7 +467,6 @@ func (api *API) FinalizeACMEOrder(w http.ResponseWriter, r *http.Request, params
|
|||||||
var certDER [][]byte
|
var certDER [][]byte
|
||||||
var certURL string
|
var certURL string
|
||||||
var cert models.PKICert
|
var cert models.PKICert
|
||||||
var existing models.PKICert
|
|
||||||
var certBlock *pem.Block
|
var certBlock *pem.Block
|
||||||
var leaf *x509.Certificate
|
var leaf *x509.Certificate
|
||||||
var challenge *acme.Challenge
|
var challenge *acme.Challenge
|
||||||
@@ -478,6 +478,7 @@ func (api *API) FinalizeACMEOrder(w http.ResponseWriter, r *http.Request, params
|
|||||||
var createdBySubjectID string
|
var createdBySubjectID string
|
||||||
var createdBySubjectName string
|
var createdBySubjectName string
|
||||||
var issuanceSource string
|
var issuanceSource string
|
||||||
|
var persistErr error
|
||||||
var err error
|
var err error
|
||||||
if !api.requireAdmin(w, r) {
|
if !api.requireAdmin(w, r) {
|
||||||
return
|
return
|
||||||
@@ -521,8 +522,7 @@ func (api *API) FinalizeACMEOrder(w http.ResponseWriter, r *http.Request, params
|
|||||||
api.Logger.Write(acmeLogID, codit_logger.LOG_WARN, "order finalize failed id=%s step=account err=%v", order.ID, err)
|
api.Logger.Write(acmeLogID, codit_logger.LOG_WARN, "order finalize failed id=%s step=account err=%v", order.ID, err)
|
||||||
order.Status = "failed"
|
order.Status = "failed"
|
||||||
order.LastError = err.Error()
|
order.LastError = err.Error()
|
||||||
_, _ = api.store(r).UpdateACMEOrder(order)
|
api.persistACMEFinalizeFailure(order, profile.ID, true)
|
||||||
_ = api.store(r).UpdateACMEProfileLastError(profile.ID, err.Error())
|
|
||||||
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, err.Error())
|
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -532,7 +532,7 @@ func (api *API) FinalizeACMEOrder(w http.ResponseWriter, r *http.Request, params
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
order.Status = "failed"
|
order.Status = "failed"
|
||||||
order.LastError = err.Error()
|
order.LastError = err.Error()
|
||||||
_, _ = api.store(r).UpdateACMEOrder(order)
|
api.persistACMEFinalizeFailure(order, profile.ID, false)
|
||||||
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, err.Error())
|
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -548,8 +548,7 @@ func (api *API) FinalizeACMEOrder(w http.ResponseWriter, r *http.Request, params
|
|||||||
order.Status = "failed"
|
order.Status = "failed"
|
||||||
order.LastError = err.Error()
|
order.LastError = err.Error()
|
||||||
order.Challenges[i].Status = "failed"
|
order.Challenges[i].Status = "failed"
|
||||||
_, _ = api.store(r).UpdateACMEOrder(order)
|
api.persistACMEFinalizeFailure(order, profile.ID, true)
|
||||||
_ = api.store(r).UpdateACMEProfileLastError(profile.ID, err.Error())
|
|
||||||
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, err.Error())
|
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -561,17 +560,23 @@ func (api *API) FinalizeACMEOrder(w http.ResponseWriter, r *http.Request, params
|
|||||||
order.Status = "failed"
|
order.Status = "failed"
|
||||||
order.LastError = err.Error()
|
order.LastError = err.Error()
|
||||||
order.Challenges[i].Status = "failed"
|
order.Challenges[i].Status = "failed"
|
||||||
_, _ = api.store(r).UpdateACMEOrder(order)
|
api.persistACMEFinalizeFailure(order, profile.ID, false)
|
||||||
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, err.Error())
|
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [NOTE]
|
||||||
|
// It may take a long time (30-120 seconds) for ACME CA to confirm DNS propagation.
|
||||||
|
// If the user closes the connection during this window, the wait is aborted. The
|
||||||
|
// upstream CA may have already verified the domain, but the order status on our
|
||||||
|
// side is still "pending". To recover this, you will have to Finalize order again.
|
||||||
authz, err = client.WaitAuthorization(r.Context(), order.Challenges[i].AuthorizationURL)
|
authz, err = client.WaitAuthorization(r.Context(), order.Challenges[i].AuthorizationURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.Logger.Write(acmeLogID, codit_logger.LOG_WARN, "challenge wait failed order=%s domain=%s err=%v", order.ID, order.Challenges[i].Identifier, err)
|
api.Logger.Write(acmeLogID, codit_logger.LOG_WARN, "challenge wait failed order=%s domain=%s err=%v", order.ID, order.Challenges[i].Identifier, err)
|
||||||
order.Status = "failed"
|
order.Status = "failed"
|
||||||
order.LastError = err.Error()
|
order.LastError = err.Error()
|
||||||
order.Challenges[i].Status = "failed"
|
order.Challenges[i].Status = "failed"
|
||||||
_, _ = api.store(r).UpdateACMEOrder(order)
|
api.persistACMEFinalizeFailure(order, profile.ID, false)
|
||||||
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, err.Error())
|
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -589,7 +594,7 @@ func (api *API) FinalizeACMEOrder(w http.ResponseWriter, r *http.Request, params
|
|||||||
api.Logger.Write(acmeLogID, codit_logger.LOG_WARN, "order finalize failed id=%s step=create_cert err=%v", order.ID, err)
|
api.Logger.Write(acmeLogID, codit_logger.LOG_WARN, "order finalize failed id=%s step=create_cert err=%v", order.ID, err)
|
||||||
order.Status = "failed"
|
order.Status = "failed"
|
||||||
order.LastError = err.Error()
|
order.LastError = err.Error()
|
||||||
_, _ = api.store(r).UpdateACMEOrder(order)
|
api.persistACMEFinalizeFailure(order, profile.ID, false)
|
||||||
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, err.Error())
|
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -598,7 +603,7 @@ func (api *API) FinalizeACMEOrder(w http.ResponseWriter, r *http.Request, params
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
order.Status = "failed"
|
order.Status = "failed"
|
||||||
order.LastError = err.Error()
|
order.LastError = err.Error()
|
||||||
_, _ = api.store(r).UpdateACMEOrder(order)
|
api.persistACMEFinalizeFailure(order, profile.ID, false)
|
||||||
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, err.Error())
|
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -607,7 +612,7 @@ func (api *API) FinalizeACMEOrder(w http.ResponseWriter, r *http.Request, params
|
|||||||
if certBlock == nil {
|
if certBlock == nil {
|
||||||
order.Status = "failed"
|
order.Status = "failed"
|
||||||
order.LastError = "invalid issued certificate"
|
order.LastError = "invalid issued certificate"
|
||||||
_, _ = api.store(r).UpdateACMEOrder(order)
|
api.persistACMEFinalizeFailure(order, profile.ID, false)
|
||||||
WriteJSON(w, http.StatusBadRequest, map[string]string{"error": order.LastError})
|
WriteJSON(w, http.StatusBadRequest, map[string]string{"error": order.LastError})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -615,7 +620,7 @@ func (api *API) FinalizeACMEOrder(w http.ResponseWriter, r *http.Request, params
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
order.Status = "failed"
|
order.Status = "failed"
|
||||||
order.LastError = err.Error()
|
order.LastError = err.Error()
|
||||||
_, _ = api.store(r).UpdateACMEOrder(order)
|
api.persistACMEFinalizeFailure(order, profile.ID, false)
|
||||||
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, err.Error())
|
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -640,46 +645,111 @@ func (api *API) FinalizeACMEOrder(w http.ResponseWriter, r *http.Request, params
|
|||||||
RevocationReason: "",
|
RevocationReason: "",
|
||||||
}
|
}
|
||||||
replaceExisting = req.RenewMode == "override"
|
replaceExisting = req.RenewMode == "override"
|
||||||
|
order.Status = "valid"
|
||||||
|
order.LastError = ""
|
||||||
|
order, cert, err = api.persistACMEFinalizeSuccess(order, profile.ID, cert, replaceExisting)
|
||||||
|
if err != nil {
|
||||||
|
api.Logger.Write(acmeLogID, codit_logger.LOG_WARN, "order finalize failed id=%s step=persist_success err=%v", order.ID, err)
|
||||||
|
order.Status = "failed"
|
||||||
|
order.LastError = err.Error()
|
||||||
|
persistErr = api.persistACMEFinalizeFailure(order, profile.ID, false)
|
||||||
|
if persistErr != nil {
|
||||||
|
api.Logger.Write(acmeLogID, codit_logger.LOG_WARN, "order finalize failure persist failed id=%s err=%v", order.ID, persistErr)
|
||||||
|
}
|
||||||
|
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = certURL
|
||||||
|
api.Logger.Write(acmeLogID, codit_logger.LOG_INFO, "order finalize success id=%s cert_id=%s cert_url=%s", order.ID, order.CertID, certURL)
|
||||||
|
WriteJSON(w, http.StatusOK, order)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) persistACMEFinalizeFailure(order models.ACMEOrder, profileID string, updateProfileError bool) error {
|
||||||
|
var ctx context.Context
|
||||||
|
var cancel context.CancelFunc
|
||||||
|
var store *db.Store
|
||||||
|
var err error
|
||||||
|
|
||||||
|
ctx, cancel = context.WithTimeout(context.Background(), 10 * time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
store, err = api.Store.BeginImmediateStore(ctx)
|
||||||
|
if err != nil {
|
||||||
|
api.Logger.Write(acmeLogID, codit_logger.LOG_WARN, "order finalize failure persist failed id=%s err=%v", order.ID, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = store.UpdateACMEOrder(order)
|
||||||
|
if err != nil {
|
||||||
|
_ = store.Rollback()
|
||||||
|
api.Logger.Write(acmeLogID, codit_logger.LOG_WARN, "order finalize failure persist failed id=%s err=%v", order.ID, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if updateProfileError {
|
||||||
|
err = store.UpdateACMEProfileLastError(profileID, order.LastError)
|
||||||
|
if err != nil {
|
||||||
|
_ = store.Rollback()
|
||||||
|
api.Logger.Write(acmeLogID, codit_logger.LOG_WARN, "order finalize profile error persist failed id=%s profile=%s err=%v", order.ID, profileID, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = store.Commit()
|
||||||
|
if err != nil {
|
||||||
|
api.Logger.Write(acmeLogID, codit_logger.LOG_WARN, "order finalize failure persist commit failed id=%s err=%v", order.ID, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) persistACMEFinalizeSuccess(order models.ACMEOrder, profileID string, cert models.PKICert, replaceExisting bool) (models.ACMEOrder, models.PKICert, error) {
|
||||||
|
var ctx context.Context
|
||||||
|
var cancel context.CancelFunc
|
||||||
|
var store *db.Store
|
||||||
|
var existing models.PKICert
|
||||||
|
var err error
|
||||||
|
|
||||||
|
ctx, cancel = context.WithTimeout(context.Background(), 10 * time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
store, err = api.Store.BeginImmediateStore(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return order, cert, err
|
||||||
|
}
|
||||||
if replaceExisting && strings.TrimSpace(order.CertID) != "" {
|
if replaceExisting && strings.TrimSpace(order.CertID) != "" {
|
||||||
existing, err = api.store(r).GetPKICert(order.CertID)
|
existing, err = store.GetPKICert(order.CertID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
cert.ID = existing.ID
|
cert.ID = existing.ID
|
||||||
cert.CAID = existing.CAID
|
cert.CAID = existing.CAID
|
||||||
cert, err = api.store(r).ReplacePKICert(cert)
|
cert, err = store.ReplacePKICert(cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.Logger.Write(acmeLogID, codit_logger.LOG_WARN, "order finalize failed id=%s step=replace_cert err=%v", order.ID, err)
|
_ = store.Rollback()
|
||||||
order.Status = "failed"
|
return order, cert, err
|
||||||
order.LastError = err.Error()
|
|
||||||
_, _ = api.store(r).UpdateACMEOrder(order)
|
|
||||||
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, err.Error())
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
api.Logger.Write(acmeLogID, codit_logger.LOG_INFO, "order finalize replaced existing cert id=%s", cert.ID)
|
api.Logger.Write(acmeLogID, codit_logger.LOG_INFO, "order finalize replaced existing cert id=%s", cert.ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(cert.ID) == "" {
|
if strings.TrimSpace(cert.ID) == "" {
|
||||||
cert, err = api.store(r).CreatePKICert(cert)
|
cert, err = store.CreatePKICert(cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.Logger.Write(acmeLogID, codit_logger.LOG_WARN, "order finalize failed id=%s step=store_cert err=%v", order.ID, err)
|
_ = store.Rollback()
|
||||||
order.Status = "failed"
|
return order, cert, err
|
||||||
order.LastError = err.Error()
|
|
||||||
_, _ = api.store(r).UpdateACMEOrder(order)
|
|
||||||
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, err.Error())
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
order.Status = "valid"
|
|
||||||
order.LastError = ""
|
|
||||||
order.CertID = cert.ID
|
order.CertID = cert.ID
|
||||||
order, err = api.store(r).UpdateACMEOrder(order)
|
order, err = store.UpdateACMEOrder(order)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
_ = store.Rollback()
|
||||||
return
|
return order, cert, err
|
||||||
}
|
}
|
||||||
_ = api.store(r).UpdateACMEProfileLastError(profile.ID, "")
|
err = store.UpdateACMEProfileLastError(profileID, "")
|
||||||
_ = certURL
|
if err != nil {
|
||||||
api.Logger.Write(acmeLogID, codit_logger.LOG_INFO, "order finalize success id=%s cert_id=%s cert_url=%s", order.ID, order.CertID, certURL)
|
_ = store.Rollback()
|
||||||
WriteJSON(w, http.StatusOK, order)
|
return order, cert, err
|
||||||
|
}
|
||||||
|
err = store.Commit()
|
||||||
|
if err != nil {
|
||||||
|
return order, cert, err
|
||||||
|
}
|
||||||
|
return order, cert, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) DeleteACMEOrder(w http.ResponseWriter, r *http.Request, params map[string]string) {
|
func (api *API) DeleteACMEOrder(w http.ResponseWriter, r *http.Request, params map[string]string) {
|
||||||
|
|||||||
+468
-233
File diff suppressed because it is too large
Load Diff
@@ -52,11 +52,11 @@ func (api *API) CreateAuthProvider(w http.ResponseWriter, r *http.Request, _ map
|
|||||||
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "name is required")
|
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "name is required")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if req.Type != db.AuthProviderTypeLDAP && req.Type != db.AuthProviderTypeOIDC {
|
if req.Type != models.AuthProviderTypeLDAP && req.Type != models.AuthProviderTypeOIDC {
|
||||||
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "type must be ldap or oidc")
|
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "type must be ldap or oidc")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if req.Type == db.AuthProviderTypeOIDC && strings.TrimSpace(req.OIDCAdmissionExpr) != "" {
|
if req.Type == models.AuthProviderTypeOIDC && strings.TrimSpace(req.OIDCAdmissionExpr) != "" {
|
||||||
err = oidcCompileAdmissionExpr(strings.TrimSpace(req.OIDCAdmissionExpr))
|
err = oidcCompileAdmissionExpr(strings.TrimSpace(req.OIDCAdmissionExpr))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "invalid admission expression: "+err.Error())
|
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "invalid admission expression: "+err.Error())
|
||||||
@@ -91,7 +91,7 @@ func (api *API) UpdateAuthProvider(w http.ResponseWriter, r *http.Request, param
|
|||||||
}
|
}
|
||||||
req.ID = existing.ID
|
req.ID = existing.ID
|
||||||
req.Type = existing.Type
|
req.Type = existing.Type
|
||||||
if req.Type == db.AuthProviderTypeOIDC && strings.TrimSpace(req.OIDCAdmissionExpr) != "" {
|
if req.Type == models.AuthProviderTypeOIDC && strings.TrimSpace(req.OIDCAdmissionExpr) != "" {
|
||||||
err = oidcCompileAdmissionExpr(strings.TrimSpace(req.OIDCAdmissionExpr))
|
err = oidcCompileAdmissionExpr(strings.TrimSpace(req.OIDCAdmissionExpr))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "invalid admission expression: "+err.Error())
|
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "invalid admission expression: "+err.Error())
|
||||||
@@ -165,7 +165,7 @@ func (api *API) TestAuthProvider(w http.ResponseWriter, r *http.Request, params
|
|||||||
WriteJSONWithErrorReason(w, r, http.StatusNotFound, "auth provider not found")
|
WriteJSONWithErrorReason(w, r, http.StatusNotFound, "auth provider not found")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if provider.Type != db.AuthProviderTypeLDAP {
|
if provider.Type != models.AuthProviderTypeLDAP {
|
||||||
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "test is only supported for ldap providers")
|
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "test is only supported for ldap providers")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ func (api *API) ListBlockComments(w http.ResponseWriter, r *http.Request, params
|
|||||||
var role string
|
var role string
|
||||||
var i int
|
var i int
|
||||||
|
|
||||||
if !api.requireBoardRole(w, r, params["boardId"], "viewer") {
|
if !api.requireBoardRole(w, r, params["boardId"], models.RoleViewer) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
comments, err = api.store(r).ListBlockComments(params["boardId"], params["blockId"])
|
comments, err = api.store(r).ListBlockComments(params["boardId"], params["blockId"])
|
||||||
@@ -42,7 +42,7 @@ func (api *API) ListBlockComments(w http.ResponseWriter, r *http.Request, params
|
|||||||
role = ""
|
role = ""
|
||||||
}
|
}
|
||||||
for i = 0; i < len(comments); i++ {
|
for i = 0; i < len(comments); i++ {
|
||||||
comments[i].CanDelete = user.IsAdmin || comments[i].CreatedBy == user.ID || boardRoleAllows(role, "admin")
|
comments[i].CanDelete = user.IsAdmin || comments[i].CreatedBy == user.ID || boardRoleAllows(role, models.RoleAdmin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WriteJSON(w, http.StatusOK, comments)
|
WriteJSON(w, http.StatusOK, comments)
|
||||||
@@ -64,7 +64,7 @@ func (api *API) CreateBlockComment(w http.ResponseWriter, r *http.Request, param
|
|||||||
WriteJSONWithErrorReason(w, r, http.StatusForbidden, "requires user account")
|
WriteJSONWithErrorReason(w, r, http.StatusForbidden, "requires user account")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !api.requireBoardRole(w, r, params["boardId"], "editor") {
|
if !api.requireBoardRole(w, r, params["boardId"], models.RoleWriter) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = DecodeJSON(r, &req)
|
err = DecodeJSON(r, &req)
|
||||||
@@ -99,7 +99,7 @@ func (api *API) DeleteBlockComment(w http.ResponseWriter, r *http.Request, param
|
|||||||
WriteJSONWithErrorReason(w, r, http.StatusForbidden, "requires user account")
|
WriteJSONWithErrorReason(w, r, http.StatusForbidden, "requires user account")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !api.requireBoardRole(w, r, params["boardId"], "viewer") {
|
if !api.requireBoardRole(w, r, params["boardId"], models.RoleViewer) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
comments, err = api.store(r).ListBlockComments(params["boardId"], params["blockId"])
|
comments, err = api.store(r).ListBlockComments(params["boardId"], params["blockId"])
|
||||||
@@ -124,7 +124,7 @@ func (api *API) DeleteBlockComment(w http.ResponseWriter, r *http.Request, param
|
|||||||
WriteJSONWithErrorReason(w, r, http.StatusForbidden, "comment deletion requires the comment author or board admin")
|
WriteJSONWithErrorReason(w, r, http.StatusForbidden, "comment deletion requires the comment author or board admin")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
canDelete = user.IsAdmin || boardRoleAllows(role, "admin")
|
canDelete = user.IsAdmin || boardRoleAllows(role, models.RoleAdmin)
|
||||||
}
|
}
|
||||||
if !canDelete {
|
if !canDelete {
|
||||||
WriteJSONWithErrorReason(w, r, http.StatusForbidden, "comment deletion requires the comment author or board admin")
|
WriteJSONWithErrorReason(w, r, http.StatusForbidden, "comment deletion requires the comment author or board admin")
|
||||||
@@ -146,7 +146,7 @@ func (api *API) ListBlockChecklistItems(w http.ResponseWriter, r *http.Request,
|
|||||||
var items []models.BlockChecklistItem
|
var items []models.BlockChecklistItem
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if !api.requireBoardRole(w, r, params["boardId"], "viewer") {
|
if !api.requireBoardRole(w, r, params["boardId"], models.RoleViewer) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
items, err = api.store(r).ListBlockChecklistItems(params["boardId"], params["blockId"])
|
items, err = api.store(r).ListBlockChecklistItems(params["boardId"], params["blockId"])
|
||||||
@@ -176,7 +176,7 @@ func (api *API) CreateBlockChecklistItem(w http.ResponseWriter, r *http.Request,
|
|||||||
WriteJSONWithErrorReason(w, r, http.StatusForbidden, "requires user account")
|
WriteJSONWithErrorReason(w, r, http.StatusForbidden, "requires user account")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !api.requireBoardRole(w, r, params["boardId"], "editor") {
|
if !api.requireBoardRole(w, r, params["boardId"], models.RoleWriter) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = DecodeJSON(r, &req)
|
err = DecodeJSON(r, &req)
|
||||||
@@ -213,7 +213,7 @@ func (api *API) UpdateBlockChecklistItem(w http.ResponseWriter, r *http.Request,
|
|||||||
WriteJSONWithErrorReason(w, r, http.StatusForbidden, "requires user account")
|
WriteJSONWithErrorReason(w, r, http.StatusForbidden, "requires user account")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !api.requireBoardRole(w, r, params["boardId"], "editor") {
|
if !api.requireBoardRole(w, r, params["boardId"], models.RoleWriter) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = DecodeJSON(r, &req)
|
err = DecodeJSON(r, &req)
|
||||||
@@ -236,7 +236,7 @@ func (api *API) UpdateBlockChecklistItem(w http.ResponseWriter, r *http.Request,
|
|||||||
func (api *API) DeleteBlockChecklistItem(w http.ResponseWriter, r *http.Request, params map[string]string) {
|
func (api *API) DeleteBlockChecklistItem(w http.ResponseWriter, r *http.Request, params map[string]string) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if !api.requireBoardRole(w, r, params["boardId"], "editor") {
|
if !api.requireBoardRole(w, r, params["boardId"], models.RoleWriter) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = api.store(r).DeleteBlockChecklistItem(params["boardId"], params["blockId"], params["itemId"])
|
err = api.store(r).DeleteBlockChecklistItem(params["boardId"], params["blockId"], params["itemId"])
|
||||||
@@ -258,7 +258,7 @@ func (api *API) ReorderBlockChecklistItems(w http.ResponseWriter, r *http.Reques
|
|||||||
var items []models.BlockChecklistItem
|
var items []models.BlockChecklistItem
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if !api.requireBoardRole(w, r, params["boardId"], "editor") {
|
if !api.requireBoardRole(w, r, params["boardId"], models.RoleWriter) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = DecodeJSON(r, &req)
|
err = DecodeJSON(r, &req)
|
||||||
@@ -289,7 +289,7 @@ func (api *API) ListBlockAttachments(w http.ResponseWriter, r *http.Request, par
|
|||||||
var items []models.BlockAttachment
|
var items []models.BlockAttachment
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if !api.requireBoardRole(w, r, params["boardId"], "viewer") {
|
if !api.requireBoardRole(w, r, params["boardId"], models.RoleViewer) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
items, err = api.store(r).ListBlockAttachments(params["boardId"], params["blockId"])
|
items, err = api.store(r).ListBlockAttachments(params["boardId"], params["blockId"])
|
||||||
@@ -320,7 +320,7 @@ func (api *API) CreateBlockAttachment(w http.ResponseWriter, r *http.Request, pa
|
|||||||
var created models.BlockAttachment
|
var created models.BlockAttachment
|
||||||
var ts string
|
var ts string
|
||||||
|
|
||||||
if !api.requireBoardRole(w, r, params["boardId"], "editor") {
|
if !api.requireBoardRole(w, r, params["boardId"], models.RoleWriter) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user, ok = middleware.UserFromContext(r.Context())
|
user, ok = middleware.UserFromContext(r.Context())
|
||||||
@@ -345,6 +345,7 @@ func (api *API) CreateBlockAttachment(w http.ResponseWriter, r *http.Request, pa
|
|||||||
tempName = storedName + ".uploading-" + ts
|
tempName = storedName + ".uploading-" + ts
|
||||||
tempPath, size, err = api.Uploads.Save(tempName, file)
|
tempPath, size, err = api.Uploads.Save(tempName, file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_ = os.Remove(tempPath)
|
||||||
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -388,7 +389,7 @@ func (api *API) DownloadBlockAttachment(w http.ResponseWriter, r *http.Request,
|
|||||||
var inline bool
|
var inline bool
|
||||||
var stat os.FileInfo
|
var stat os.FileInfo
|
||||||
|
|
||||||
if !api.requireBoardRole(w, r, params["boardId"], "viewer") {
|
if !api.requireBoardRole(w, r, params["boardId"], models.RoleViewer) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
item, err = api.store(r).GetBlockAttachment(params["boardId"], params["blockId"], params["attachmentId"])
|
item, err = api.store(r).GetBlockAttachment(params["boardId"], params["blockId"], params["attachmentId"])
|
||||||
@@ -420,7 +421,7 @@ func (api *API) DeleteBlockAttachment(w http.ResponseWriter, r *http.Request, pa
|
|||||||
var item models.BlockAttachment
|
var item models.BlockAttachment
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if !api.requireBoardRole(w, r, params["boardId"], "editor") {
|
if !api.requireBoardRole(w, r, params["boardId"], models.RoleWriter) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
item, err = api.store(r).DeleteBlockAttachment(r.Context(), params["boardId"], params["blockId"], params["attachmentId"])
|
item, err = api.store(r).DeleteBlockAttachment(r.Context(), params["boardId"], params["blockId"], params["attachmentId"])
|
||||||
@@ -440,7 +441,7 @@ func (api *API) ListBoardBlockProperties(w http.ResponseWriter, r *http.Request,
|
|||||||
var props []models.BlockProperties
|
var props []models.BlockProperties
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if !api.requireBoardRole(w, r, params["boardId"], "viewer") {
|
if !api.requireBoardRole(w, r, params["boardId"], models.RoleViewer) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
props, err = api.store(r).ListAllBlockProperties(params["boardId"])
|
props, err = api.store(r).ListAllBlockProperties(params["boardId"])
|
||||||
@@ -458,7 +459,7 @@ func (api *API) GetBlockProperties(w http.ResponseWriter, r *http.Request, param
|
|||||||
var bp models.BlockProperties
|
var bp models.BlockProperties
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if !api.requireBoardRole(w, r, params["boardId"], "viewer") {
|
if !api.requireBoardRole(w, r, params["boardId"], models.RoleViewer) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
bp, err = api.store(r).GetBlockProperties(params["boardId"], params["blockId"])
|
bp, err = api.store(r).GetBlockProperties(params["boardId"], params["blockId"])
|
||||||
@@ -489,7 +490,7 @@ func (api *API) UpsertBlockProperties(w http.ResponseWriter, r *http.Request, pa
|
|||||||
var bp models.BlockProperties
|
var bp models.BlockProperties
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if !api.requireBoardRole(w, r, params["boardId"], "editor") {
|
if !api.requireBoardRole(w, r, params["boardId"], models.RoleWriter) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = DecodeJSON(r, &req)
|
err = DecodeJSON(r, &req)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ func (api *API) ListBoardFieldValues(w http.ResponseWriter, r *http.Request, par
|
|||||||
var values []models.BoardFieldValue
|
var values []models.BoardFieldValue
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if !api.requireBoardRole(w, r, params["boardId"], "viewer") {
|
if !api.requireBoardRole(w, r, params["boardId"], models.RoleViewer) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
values, err = api.store(r).ListBoardFieldValues(params["boardId"], params["field"])
|
values, err = api.store(r).ListBoardFieldValues(params["boardId"], params["field"])
|
||||||
@@ -39,7 +39,7 @@ func (api *API) CreateBoardFieldValue(w http.ResponseWriter, r *http.Request, pa
|
|||||||
var created models.BoardFieldValue
|
var created models.BoardFieldValue
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if !api.requireBoardRole(w, r, params["boardId"], "admin") {
|
if !api.requireBoardRole(w, r, params["boardId"], models.RoleAdmin) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = DecodeJSON(r, &req)
|
err = DecodeJSON(r, &req)
|
||||||
@@ -73,7 +73,7 @@ func (api *API) UpdateBoardFieldValue(w http.ResponseWriter, r *http.Request, pa
|
|||||||
var updated models.BoardFieldValue
|
var updated models.BoardFieldValue
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if !api.requireBoardRole(w, r, params["boardId"], "admin") {
|
if !api.requireBoardRole(w, r, params["boardId"], models.RoleAdmin) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = DecodeJSON(r, &req)
|
err = DecodeJSON(r, &req)
|
||||||
@@ -106,7 +106,7 @@ func (api *API) ReorderBoardFieldValues(w http.ResponseWriter, r *http.Request,
|
|||||||
var values []models.BoardFieldValue
|
var values []models.BoardFieldValue
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if !api.requireBoardRole(w, r, params["boardId"], "admin") {
|
if !api.requireBoardRole(w, r, params["boardId"], models.RoleAdmin) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = DecodeJSON(r, &req)
|
err = DecodeJSON(r, &req)
|
||||||
@@ -132,7 +132,7 @@ func (api *API) ReorderBoardFieldValues(w http.ResponseWriter, r *http.Request,
|
|||||||
func (api *API) DeleteBoardFieldValue(w http.ResponseWriter, r *http.Request, params map[string]string) {
|
func (api *API) DeleteBoardFieldValue(w http.ResponseWriter, r *http.Request, params map[string]string) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if !api.requireBoardRole(w, r, params["boardId"], "admin") {
|
if !api.requireBoardRole(w, r, params["boardId"], models.RoleAdmin) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = api.store(r).DeleteBoardFieldValue(r.Context(), params["boardId"], params["valueId"])
|
err = api.store(r).DeleteBoardFieldValue(r.Context(), params["boardId"], params["valueId"])
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ import "codit/internal/models"
|
|||||||
// boardRoleRank returns a numeric rank for board roles (higher = more access).
|
// boardRoleRank returns a numeric rank for board roles (higher = more access).
|
||||||
func boardRoleRank(role string) int {
|
func boardRoleRank(role string) int {
|
||||||
switch role {
|
switch role {
|
||||||
case "admin":
|
case models.RoleAdmin:
|
||||||
return 3
|
return 3
|
||||||
case "editor":
|
case models.RoleWriter:
|
||||||
return 2
|
return 2
|
||||||
case "viewer":
|
case models.RoleViewer:
|
||||||
return 1
|
return 1
|
||||||
default:
|
default:
|
||||||
return 0
|
return 0
|
||||||
@@ -24,7 +24,7 @@ func boardRoleRank(role string) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// boardRoleAllows checks whether actual satisfies the required board role.
|
// boardRoleAllows checks whether actual satisfies the required board role.
|
||||||
// Hierarchy: admin > editor > viewer.
|
// Hierarchy: admin > writer > viewer.
|
||||||
func boardRoleAllows(actual, required string) bool {
|
func boardRoleAllows(actual, required string) bool {
|
||||||
return boardRoleRank(actual) >= boardRoleRank(required)
|
return boardRoleRank(actual) >= boardRoleRank(required)
|
||||||
}
|
}
|
||||||
@@ -38,7 +38,7 @@ func (api *API) boardRoleForUser(r *http.Request, boardID string, user models.Us
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
if user.IsAdmin {
|
if user.IsAdmin {
|
||||||
return "admin", nil
|
return models.RoleAdmin, nil
|
||||||
}
|
}
|
||||||
projectID, err = api.store(r).GetBoardProjectID(boardID)
|
projectID, err = api.store(r).GetBoardProjectID(boardID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -61,12 +61,12 @@ func (api *API) boardRoleForUser(r *http.Request, boardID string, user models.Us
|
|||||||
|
|
||||||
func projectRoleToBoardRole(projectRole string) string {
|
func projectRoleToBoardRole(projectRole string) string {
|
||||||
switch projectRole {
|
switch projectRole {
|
||||||
case "admin":
|
case models.RoleAdmin:
|
||||||
return "admin"
|
return models.RoleAdmin
|
||||||
case "writer":
|
case models.RoleWriter:
|
||||||
return "editor"
|
return models.RoleWriter
|
||||||
default:
|
default:
|
||||||
return "viewer"
|
return models.RoleViewer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ func projectRoleToBoardRole(projectRole string) string {
|
|||||||
// Returns "" for unknown roles.
|
// Returns "" for unknown roles.
|
||||||
func normalizeBoardRole(role string) string {
|
func normalizeBoardRole(role string) string {
|
||||||
switch role {
|
switch role {
|
||||||
case "admin", "editor", "viewer":
|
case models.RoleAdmin, models.RoleWriter, models.RoleViewer:
|
||||||
return role
|
return role
|
||||||
default:
|
default:
|
||||||
return ""
|
return ""
|
||||||
@@ -240,7 +240,7 @@ func (api *API) CreateBoard(w http.ResponseWriter, r *http.Request, params map[s
|
|||||||
WriteJSONWithErrorReason(w, r, http.StatusForbidden, "board creation requires a user account")
|
WriteJSONWithErrorReason(w, r, http.StatusForbidden, "board creation requires a user account")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !api.requireProjectRole(w, r, params["projectId"], "writer") {
|
if !api.requireProjectRole(w, r, params["projectId"], models.RoleWriter) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = DecodeJSON(r, &req)
|
err = DecodeJSON(r, &req)
|
||||||
@@ -286,7 +286,7 @@ func (api *API) GetBoard(w http.ResponseWriter, r *http.Request, params map[stri
|
|||||||
WriteJSONWithErrorReason(w, r, http.StatusNotFound, "board not found")
|
WriteJSONWithErrorReason(w, r, http.StatusNotFound, "board not found")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !api.requireBoardRole(w, r, board.ID, "viewer") {
|
if !api.requireBoardRole(w, r, board.ID, models.RoleViewer) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
WriteJSON(w, http.StatusOK, board)
|
WriteJSON(w, http.StatusOK, board)
|
||||||
@@ -310,7 +310,7 @@ func (api *API) UpdateBoard(w http.ResponseWriter, r *http.Request, params map[s
|
|||||||
WriteJSONWithErrorReason(w, r, http.StatusNotFound, "board not found")
|
WriteJSONWithErrorReason(w, r, http.StatusNotFound, "board not found")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !api.requireBoardRole(w, r, board.ID, "editor") {
|
if !api.requireBoardRole(w, r, board.ID, models.RoleWriter) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = DecodeJSON(r, &req)
|
err = DecodeJSON(r, &req)
|
||||||
@@ -358,7 +358,7 @@ func (api *API) DeleteBoard(w http.ResponseWriter, r *http.Request, params map[s
|
|||||||
WriteJSONWithErrorReason(w, r, http.StatusNotFound, "board not found")
|
WriteJSONWithErrorReason(w, r, http.StatusNotFound, "board not found")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !api.requireBoardRole(w, r, board.ID, "admin") {
|
if !api.requireBoardRole(w, r, board.ID, models.RoleAdmin) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = api.store(r).DeleteBoard(board.ID, user.ID)
|
err = api.store(r).DeleteBoard(board.ID, user.ID)
|
||||||
@@ -390,7 +390,7 @@ func (api *API) ListBlocks(w http.ResponseWriter, r *http.Request, params map[st
|
|||||||
var err error
|
var err error
|
||||||
var blockType string
|
var blockType string
|
||||||
|
|
||||||
if !api.requireBoardRole(w, r, params["boardId"], "viewer") {
|
if !api.requireBoardRole(w, r, params["boardId"], models.RoleViewer) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
blockType = r.URL.Query().Get("type")
|
blockType = r.URL.Query().Get("type")
|
||||||
@@ -422,7 +422,7 @@ func (api *API) CreateBlock(w http.ResponseWriter, r *http.Request, params map[s
|
|||||||
WriteJSONWithErrorReason(w, r, http.StatusForbidden, "block creation requires a user account")
|
WriteJSONWithErrorReason(w, r, http.StatusForbidden, "block creation requires a user account")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !api.requireBoardRole(w, r, params["boardId"], "editor") {
|
if !api.requireBoardRole(w, r, params["boardId"], models.RoleWriter) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = DecodeJSON(r, &req)
|
err = DecodeJSON(r, &req)
|
||||||
@@ -459,7 +459,7 @@ func (api *API) GetBlock(w http.ResponseWriter, r *http.Request, params map[stri
|
|||||||
var block models.Block
|
var block models.Block
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if !api.requireBoardRole(w, r, params["boardId"], "viewer") {
|
if !api.requireBoardRole(w, r, params["boardId"], models.RoleViewer) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
block, err = api.store(r).GetBlock(params["boardId"], params["blockId"])
|
block, err = api.store(r).GetBlock(params["boardId"], params["blockId"])
|
||||||
@@ -483,7 +483,7 @@ func (api *API) UpdateBlock(w http.ResponseWriter, r *http.Request, params map[s
|
|||||||
WriteJSONWithErrorReason(w, r, http.StatusForbidden, "block updates require a user account")
|
WriteJSONWithErrorReason(w, r, http.StatusForbidden, "block updates require a user account")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !api.requireBoardRole(w, r, params["boardId"], "editor") {
|
if !api.requireBoardRole(w, r, params["boardId"], models.RoleWriter) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
block, err = api.store(r).GetBlock(params["boardId"], params["blockId"])
|
block, err = api.store(r).GetBlock(params["boardId"], params["blockId"])
|
||||||
@@ -538,7 +538,7 @@ func (api *API) PatchBlocks(w http.ResponseWriter, r *http.Request, params map[s
|
|||||||
WriteJSONWithErrorReason(w, r, http.StatusForbidden, "block updates require a user account")
|
WriteJSONWithErrorReason(w, r, http.StatusForbidden, "block updates require a user account")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !api.requireBoardRole(w, r, params["boardId"], "editor") {
|
if !api.requireBoardRole(w, r, params["boardId"], models.RoleWriter) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = DecodeJSON(r, &patches)
|
err = DecodeJSON(r, &patches)
|
||||||
@@ -572,7 +572,7 @@ func (api *API) DeleteBlock(w http.ResponseWriter, r *http.Request, params map[s
|
|||||||
WriteJSONWithErrorReason(w, r, http.StatusForbidden, "block deletion requires a user account")
|
WriteJSONWithErrorReason(w, r, http.StatusForbidden, "block deletion requires a user account")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !api.requireBoardRole(w, r, params["boardId"], "editor") {
|
if !api.requireBoardRole(w, r, params["boardId"], models.RoleWriter) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = api.store(r).DeleteBlock(r.Context(), params["boardId"], params["blockId"], user.ID)
|
err = api.store(r).DeleteBlock(r.Context(), params["boardId"], params["blockId"], user.ID)
|
||||||
@@ -598,7 +598,7 @@ func (api *API) ListBoardMembers(w http.ResponseWriter, r *http.Request, params
|
|||||||
var members []models.BoardMember
|
var members []models.BoardMember
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if !api.requireBoardRole(w, r, params["boardId"], "admin") {
|
if !api.requireBoardRole(w, r, params["boardId"], models.RoleAdmin) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
members, err = api.store(r).ListBoardMembers(params["boardId"])
|
members, err = api.store(r).ListBoardMembers(params["boardId"])
|
||||||
@@ -616,7 +616,7 @@ func (api *API) ListBoardAssignableUsers(w http.ResponseWriter, r *http.Request,
|
|||||||
var users []models.BoardAssignableUser
|
var users []models.BoardAssignableUser
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if !api.requireBoardRole(w, r, params["boardId"], "viewer") {
|
if !api.requireBoardRole(w, r, params["boardId"], models.RoleViewer) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
users, err = api.store(r).ListBoardAssignableUsers(params["boardId"])
|
users, err = api.store(r).ListBoardAssignableUsers(params["boardId"])
|
||||||
@@ -636,7 +636,7 @@ func (api *API) AddBoardMember(w http.ResponseWriter, r *http.Request, params ma
|
|||||||
var member models.BoardMember
|
var member models.BoardMember
|
||||||
var role string
|
var role string
|
||||||
|
|
||||||
if !api.requireBoardRole(w, r, params["boardId"], "admin") {
|
if !api.requireBoardRole(w, r, params["boardId"], models.RoleAdmin) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = DecodeJSON(r, &req)
|
err = DecodeJSON(r, &req)
|
||||||
@@ -649,11 +649,11 @@ func (api *API) AddBoardMember(w http.ResponseWriter, r *http.Request, params ma
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if req.Role == "" {
|
if req.Role == "" {
|
||||||
req.Role = "editor"
|
req.Role = models.RoleWriter
|
||||||
}
|
}
|
||||||
role = normalizeBoardRole(req.Role)
|
role = normalizeBoardRole(req.Role)
|
||||||
if role == "" {
|
if role == "" {
|
||||||
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "role must be admin, editor, or viewer")
|
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "role must be admin, writer, or viewer")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
member = models.BoardMember{
|
member = models.BoardMember{
|
||||||
@@ -679,7 +679,7 @@ func (api *API) UpdateBoardMember(w http.ResponseWriter, r *http.Request, params
|
|||||||
var member models.BoardMember
|
var member models.BoardMember
|
||||||
var role string
|
var role string
|
||||||
|
|
||||||
if !api.requireBoardRole(w, r, params["boardId"], "admin") {
|
if !api.requireBoardRole(w, r, params["boardId"], models.RoleAdmin) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = DecodeJSON(r, &req)
|
err = DecodeJSON(r, &req)
|
||||||
@@ -689,7 +689,7 @@ func (api *API) UpdateBoardMember(w http.ResponseWriter, r *http.Request, params
|
|||||||
}
|
}
|
||||||
role = normalizeBoardRole(req.Role)
|
role = normalizeBoardRole(req.Role)
|
||||||
if role == "" {
|
if role == "" {
|
||||||
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "role must be admin, editor, or viewer")
|
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "role must be admin, writer, or viewer")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
member = models.BoardMember{
|
member = models.BoardMember{
|
||||||
@@ -712,7 +712,7 @@ func (api *API) UpdateBoardMember(w http.ResponseWriter, r *http.Request, params
|
|||||||
func (api *API) RemoveBoardMember(w http.ResponseWriter, r *http.Request, params map[string]string) {
|
func (api *API) RemoveBoardMember(w http.ResponseWriter, r *http.Request, params map[string]string) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if !api.requireBoardRole(w, r, params["boardId"], "admin") {
|
if !api.requireBoardRole(w, r, params["boardId"], models.RoleAdmin) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = api.store(r).DeleteBoardMember(params["boardId"], params["userId"])
|
err = api.store(r).DeleteBoardMember(params["boardId"], params["userId"])
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func (api *API) ListPublicAuthProviders(w http.ResponseWriter, r *http.Request,
|
|||||||
}
|
}
|
||||||
result = make([]map[string]string, 0, len(providers))
|
result = make([]map[string]string, 0, len(providers))
|
||||||
for _, p = range providers {
|
for _, p = range providers {
|
||||||
if p.Type == db.AuthProviderTypeDB {
|
if p.Type == models.AuthProviderTypeDB {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
result = append(result, map[string]string{
|
result = append(result, map[string]string{
|
||||||
@@ -76,7 +76,7 @@ func (api *API) OIDCLoginWithProvider(w http.ResponseWriter, r *http.Request, pa
|
|||||||
var err error
|
var err error
|
||||||
var authURL string
|
var authURL string
|
||||||
provider, err = api.store(r).GetAuthProvider(params["id"])
|
provider, err = api.store(r).GetAuthProvider(params["id"])
|
||||||
if err != nil || provider.Type != db.AuthProviderTypeOIDC {
|
if err != nil || provider.Type != models.AuthProviderTypeOIDC {
|
||||||
WriteJSONWithErrorReason(w, r, http.StatusNotFound, "oidc provider not found")
|
WriteJSONWithErrorReason(w, r, http.StatusNotFound, "oidc provider not found")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -117,8 +117,12 @@ func (api *API) OIDCCallbackWithProvider(w http.ResponseWriter, r *http.Request,
|
|||||||
var user models.User
|
var user models.User
|
||||||
var required bool
|
var required bool
|
||||||
var sessionVerified bool
|
var sessionVerified bool
|
||||||
|
var txStore *db.Store
|
||||||
|
var tokenValue string
|
||||||
|
var expires time.Time
|
||||||
|
|
||||||
provider, err = api.store(r).GetAuthProvider(params["id"])
|
provider, err = api.store(r).GetAuthProvider(params["id"])
|
||||||
if err != nil || provider.Type != db.AuthProviderTypeOIDC {
|
if err != nil || provider.Type != models.AuthProviderTypeOIDC {
|
||||||
WriteJSONWithErrorReason(w, r, http.StatusNotFound, "oidc provider not found")
|
WriteJSONWithErrorReason(w, r, http.StatusNotFound, "oidc provider not found")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -142,42 +146,63 @@ func (api *API) OIDCCallbackWithProvider(w http.ResponseWriter, r *http.Request,
|
|||||||
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "missing authorization code")
|
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "missing authorization code")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this is an external http call - it must stay outside a transaction
|
||||||
token, err = api.oidcExchangeCodeFromProvider(r.Context(), provider, code)
|
token, err = api.oidcExchangeCodeFromProvider(r.Context(), provider, code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.Logger.Write(logIDAuth, codit_logger.LOG_WARN, "oidc token exchange failed provider=%s err=%v", provider.Name, err)
|
api.Logger.Write(logIDAuth, codit_logger.LOG_WARN, "oidc token exchange failed provider=%s err=%v", provider.Name, err)
|
||||||
WriteJSONWithErrorReason(w, r, http.StatusUnauthorized, err.Error())
|
WriteJSONWithErrorReason(w, r, http.StatusUnauthorized, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this is another external http call - it must stay outside a transaction
|
||||||
claims, err = api.oidcResolveClaimsFromProvider(r.Context(), provider, token)
|
claims, err = api.oidcResolveClaimsFromProvider(r.Context(), provider, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.Logger.Write(logIDAuth, codit_logger.LOG_WARN, "oidc claims fetch failed provider=%s err=%v", provider.Name, err)
|
api.Logger.Write(logIDAuth, codit_logger.LOG_WARN, "oidc claims fetch failed provider=%s err=%v", provider.Name, err)
|
||||||
WriteJSONWithErrorReason(w, r, http.StatusUnauthorized, "oidc claims fetch failed")
|
WriteJSONWithErrorReason(w, r, http.StatusUnauthorized, "oidc claims fetch failed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user, err = api.oidcGetOrCreateUser(r.Context(), claims, provider)
|
|
||||||
if err != nil {
|
txStore, err = api.Store.BeginImmediateStore(r.Context())
|
||||||
api.Logger.Write(logIDAuth, codit_logger.LOG_WARN, "oidc user mapping failed provider=%s err=%v", provider.Name, err)
|
|
||||||
WriteJSONWithErrorReason(w, r, http.StatusUnauthorized, fmt.Sprintf("oidc user mapping failed - %s", err.Error()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
required, err = api.store(r).UserRequiresTOTP(user.ID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user, err = api.store(r).GetUserByID(user.ID)
|
user, err = api.oidcGetOrCreateUser(r.Context(), txStore, claims, provider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_ = txStore.Rollback()
|
||||||
|
api.Logger.Write(logIDAuth, codit_logger.LOG_WARN, "oidc user mapping failed provider=%s err=%v", provider.Name, err)
|
||||||
|
WriteJSONWithErrorReason(w, r, http.StatusUnauthorized, fmt.Sprintf("oidc user mapping failed - %s", err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
required, err = txStore.UserRequiresTOTP(user.ID)
|
||||||
|
if err != nil {
|
||||||
|
_ = txStore.Rollback()
|
||||||
|
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user, err = txStore.GetUserByID(user.ID)
|
||||||
|
if err != nil {
|
||||||
|
_ = txStore.Rollback()
|
||||||
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user.TOTPEffectiveRequired = required
|
user.TOTPEffectiveRequired = required
|
||||||
user.TOTPSetupRequired = required && !user.TOTPEnabled
|
user.TOTPSetupRequired = required && !user.TOTPEnabled
|
||||||
sessionVerified = !required
|
sessionVerified = !required
|
||||||
err = api.issueSession(w, r, user, sessionVerified, token.IDToken)
|
tokenValue, expires, err = api.createSessionWithStore(txStore, user, sessionVerified, token.IDToken)
|
||||||
|
if err != nil {
|
||||||
|
_ = txStore.Rollback()
|
||||||
|
api.Logger.Write(logIDAuth, codit_logger.LOG_WARN, "session create failed user=%s err=%v", user.Username, err)
|
||||||
|
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = txStore.Commit()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
api.setSessionCookie(w, tokenValue, expires)
|
||||||
api.Logger.Write(logIDAuth, codit_logger.LOG_INFO, "oidc login success username=%s provider=%s", user.Username, provider.Name)
|
api.Logger.Write(logIDAuth, codit_logger.LOG_INFO, "oidc login success username=%s provider=%s", user.Username, provider.Name)
|
||||||
http.Redirect(w, r, "/", http.StatusFound)
|
http.Redirect(w, r, "/", http.StatusFound)
|
||||||
}
|
}
|
||||||
@@ -373,7 +398,7 @@ func (api *API) oidcUserInfoFromProvider(ctx context.Context, p models.AuthProvi
|
|||||||
return claims, nil
|
return claims, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) oidcGetOrCreateUser(ctx context.Context, claims oidcUserClaims, provider models.AuthProvider) (models.User, error) {
|
func (api *API) oidcGetOrCreateUser(ctx context.Context, store *db.Store, claims oidcUserClaims, provider models.AuthProvider) (models.User, error) {
|
||||||
var username string
|
var username string
|
||||||
var displayName string
|
var displayName string
|
||||||
var email string
|
var email string
|
||||||
@@ -386,12 +411,12 @@ func (api *API) oidcGetOrCreateUser(ctx context.Context, claims oidcUserClaims,
|
|||||||
return user, err
|
return user, err
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err = api.storeContext(ctx).GetUserByProviderAndSub(provider.ID, claims.Sub)
|
user, err = store.GetUserByProviderAndSub(provider.ID, claims.Sub)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if user.Disabled {
|
if user.Disabled {
|
||||||
return user, errors.New("user disabled")
|
return user, errors.New("user disabled")
|
||||||
}
|
}
|
||||||
err = api.oidcSyncUserGroups(ctx, provider, user, claims, false)
|
err = api.oidcSyncUserGroups(ctx, store, provider, user, claims, false)
|
||||||
if err != nil { return user, err }
|
if err != nil { return user, err }
|
||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
@@ -411,15 +436,15 @@ func (api *API) oidcGetOrCreateUser(ctx context.Context, claims oidcUserClaims,
|
|||||||
AuthProviderID: provider.ID,
|
AuthProviderID: provider.ID,
|
||||||
ExternalSubject: strings.TrimSpace(claims.Sub),
|
ExternalSubject: strings.TrimSpace(claims.Sub),
|
||||||
}
|
}
|
||||||
created, err = api.storeContext(ctx).CreateUser(user, "")
|
created, err = store.CreateUser(user, "")
|
||||||
if err != nil { return created, err }
|
if err != nil { return created, err }
|
||||||
|
|
||||||
err = api.oidcSyncUserGroups(ctx, provider, created, claims, true)
|
err = api.oidcSyncUserGroups(ctx, store, provider, created, claims, true)
|
||||||
if err != nil { return created, err }
|
if err != nil { return created, err }
|
||||||
return created, nil
|
return created, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) oidcSyncUserGroups(ctx context.Context, provider models.AuthProvider, user models.User, claims oidcUserClaims, isNew bool) error {
|
func (api *API) oidcSyncUserGroups(ctx context.Context, store *db.Store, provider models.AuthProvider, user models.User, claims oidcUserClaims, isNew bool) error {
|
||||||
var mode string
|
var mode string
|
||||||
var groupsClaim string
|
var groupsClaim string
|
||||||
var claimValues []string
|
var claimValues []string
|
||||||
@@ -433,17 +458,17 @@ func (api *API) oidcSyncUserGroups(ctx context.Context, provider models.AuthProv
|
|||||||
var matched bool
|
var matched bool
|
||||||
|
|
||||||
mode = strings.TrimSpace(provider.GroupSyncMode)
|
mode = strings.TrimSpace(provider.GroupSyncMode)
|
||||||
if mode == db.GroupSyncModeOff || mode == "" {
|
if mode == models.GroupSyncModeOff || mode == "" {
|
||||||
if isNew && strings.TrimSpace(provider.DefaultUserGroupID) != "" {
|
if isNew && strings.TrimSpace(provider.DefaultUserGroupID) != "" {
|
||||||
err = api.storeContext(ctx).AddUserGroupMember(ctx, provider.DefaultUserGroupID, user.ID)
|
err = store.AddUserGroupMember(ctx, provider.DefaultUserGroupID, user.ID)
|
||||||
if err != nil { return fmt.Errorf("oidc default user group assignment failed: %w", err) }
|
if err != nil { return fmt.Errorf("oidc default user group assignment failed: %w", err) }
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if mode == db.GroupSyncModeFirstLogin && !isNew {
|
if mode == models.GroupSyncModeFirstLogin && !isNew {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if mode != db.GroupSyncModeFirstLogin && mode != db.GroupSyncModeSync {
|
if mode != models.GroupSyncModeFirstLogin && mode != models.GroupSyncModeSync {
|
||||||
return fmt.Errorf("invalid oidc group sync mode: %s", mode)
|
return fmt.Errorf("invalid oidc group sync mode: %s", mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -457,7 +482,7 @@ func (api *API) oidcSyncUserGroups(ctx context.Context, provider models.AuthProv
|
|||||||
claimSet[claimValues[i]] = true
|
claimSet[claimValues[i]] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
mappings, err = api.storeContext(ctx).ListAuthProviderGroupMappings(provider.ID)
|
mappings, err = store.ListAuthProviderGroupMappings(provider.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("oidc group mapping lookup failed: %w", err)
|
return fmt.Errorf("oidc group mapping lookup failed: %w", err)
|
||||||
}
|
}
|
||||||
@@ -466,17 +491,17 @@ func (api *API) oidcSyncUserGroups(ctx context.Context, provider models.AuthProv
|
|||||||
assignedAny = false
|
assignedAny = false
|
||||||
for groupID, matched = range groupMatched {
|
for groupID, matched = range groupMatched {
|
||||||
if matched {
|
if matched {
|
||||||
err = api.storeContext(ctx).AddUserGroupMember(ctx, groupID, user.ID)
|
err = store.AddUserGroupMember(ctx, groupID, user.ID)
|
||||||
if err != nil { return fmt.Errorf("oidc sso group assignment failed: %w", err) }
|
if err != nil { return fmt.Errorf("oidc sso group assignment failed: %w", err) }
|
||||||
assignedAny = true
|
assignedAny = true
|
||||||
} else if mode == db.GroupSyncModeSync {
|
} else if mode == models.GroupSyncModeSync {
|
||||||
err = api.storeContext(ctx).RemoveUserGroupMember(ctx, groupID, user.ID)
|
err = store.RemoveUserGroupMember(ctx, groupID, user.ID)
|
||||||
if err != nil { return fmt.Errorf("oidc sso group removal failed: %w", err) }
|
if err != nil { return fmt.Errorf("oidc sso group removal failed: %w", err) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if isNew && !assignedAny && strings.TrimSpace(provider.DefaultUserGroupID) != "" {
|
if isNew && !assignedAny && strings.TrimSpace(provider.DefaultUserGroupID) != "" {
|
||||||
err = api.storeContext(ctx).AddUserGroupMember(ctx, provider.DefaultUserGroupID, user.ID)
|
err = store.AddUserGroupMember(ctx, provider.DefaultUserGroupID, user.ID)
|
||||||
if err != nil { return fmt.Errorf("oidc default user group assignment failed: %w", err) }
|
if err != nil { return fmt.Errorf("oidc default user group assignment failed: %w", err) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -159,13 +159,18 @@ func (api *API) startSSHFileTransferAudit(store *db.Store, r *http.Request, oper
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) finishSSHFileTransferAudit(audit *sshFileTransferAudit, status string) {
|
func (api *API) finishSSHFileTransferAudit(audit *sshFileTransferAudit, status string) {
|
||||||
|
var ctx context.Context
|
||||||
|
var cancel context.CancelFunc
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if audit == nil || audit.Finished { return }
|
if audit == nil || audit.Finished { return }
|
||||||
audit.Finished = true
|
audit.Finished = true
|
||||||
|
|
||||||
// i don't bind this to a request context because i want the audit record write to persist regardless of client connection.
|
// Use an independent bounded context so client cancellation does not skip the final audit update.
|
||||||
err = audit.Store.FinishSSHFileTransfer(context.Background(), audit.Item.ID, status, audit.Paths, audit.Bytes, audit.ErrorText)
|
ctx, cancel = context.WithTimeout(context.Background(), 10 * time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
err = audit.Store.FinishSSHFileTransfer(ctx, audit.Item.ID, status, audit.Paths, audit.Bytes, audit.ErrorText)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.Logger.Write(logIDSSHBroker, codit_logger.LOG_WARN, "ssh file transfer audit finish failed id=%s op=%s err=%s", audit.Item.ID, audit.Item.Operation, err.Error())
|
api.Logger.Write(logIDSSHBroker, codit_logger.LOG_WARN, "ssh file transfer audit finish failed id=%s op=%s err=%s", audit.Item.ID, audit.Item.Operation, err.Error())
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1600,9 +1600,8 @@ func (api *API) DiscoverSSHServerHostKeyAdmin(w http.ResponseWriter, r *http.Req
|
|||||||
var discovered models.SSHServerHostKey
|
var discovered models.SSHServerHostKey
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if !api.requireAdmin(w, r) {
|
if !api.requireAdmin(w, r) { return }
|
||||||
return
|
|
||||||
}
|
|
||||||
item, err = api.store(r).GetSSHServer(params["id"])
|
item, err = api.store(r).GetSSHServer(params["id"])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
@@ -2540,20 +2539,14 @@ func discoverSSHServerHostKey(host string, port int, defaultUser string) (models
|
|||||||
Timeout: 10 * time.Second,
|
Timeout: 10 * time.Second,
|
||||||
}
|
}
|
||||||
conn, err = net.DialTimeout("tcp", addr, 10*time.Second)
|
conn, err = net.DialTimeout("tcp", addr, 10*time.Second)
|
||||||
if err != nil {
|
if err != nil { return item, err }
|
||||||
return item, err
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
clientConn, _, reqs, err = ssh.NewClientConn(conn, addr, config)
|
clientConn, _, reqs, err = ssh.NewClientConn(conn, addr, config)
|
||||||
if clientConn != nil {
|
if clientConn != nil { clientConn.Close() }
|
||||||
clientConn.Close()
|
if reqs != nil { go ssh.DiscardRequests(reqs) }
|
||||||
}
|
if discovered == nil { return item, errors.New("failed to discover ssh host key") }
|
||||||
if reqs != nil {
|
|
||||||
go ssh.DiscardRequests(reqs)
|
|
||||||
}
|
|
||||||
if discovered == nil {
|
|
||||||
return item, errors.New("failed to discover ssh host key")
|
|
||||||
}
|
|
||||||
item.Algorithm = discovered.Type()
|
item.Algorithm = discovered.Type()
|
||||||
item.PublicKey = strings.TrimSpace(string(ssh.MarshalAuthorizedKey(discovered)))
|
item.PublicKey = strings.TrimSpace(string(ssh.MarshalAuthorizedKey(discovered)))
|
||||||
item.Fingerprint = strings.TrimSpace(ssh.FingerprintSHA256(discovered))
|
item.Fingerprint = strings.TrimSpace(ssh.FingerprintSHA256(discovered))
|
||||||
|
|||||||
@@ -556,6 +556,7 @@ func (api *API) InspectSSHCertificate(w http.ResponseWriter, r *http.Request, _
|
|||||||
var cert *ssh.Certificate
|
var cert *ssh.Certificate
|
||||||
var dump string
|
var dump string
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
user, ok = middleware.UserFromContext(r.Context())
|
user, ok = middleware.UserFromContext(r.Context())
|
||||||
if !ok || user.Disabled {
|
if !ok || user.Disabled {
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
|
import codit_logger "codit/logger"
|
||||||
import "database/sql"
|
import "database/sql"
|
||||||
import "net/http"
|
import "net/http"
|
||||||
import "net/url"
|
import "net/url"
|
||||||
@@ -37,19 +38,42 @@ func (api *API) finishLogin(w http.ResponseWriter, r *http.Request, user models.
|
|||||||
var err error
|
var err error
|
||||||
var required bool
|
var required bool
|
||||||
var sessionVerified bool
|
var sessionVerified bool
|
||||||
required, err = api.store(r).UserRequiresTOTP(user.ID)
|
var txStore *db.Store
|
||||||
|
var token string
|
||||||
|
var expires time.Time
|
||||||
|
|
||||||
|
txStore, err = api.Store.BeginImmediateStore(r.Context())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
required, err = txStore.UserRequiresTOTP(user.ID)
|
||||||
|
if err != nil {
|
||||||
|
_ = txStore.Rollback()
|
||||||
|
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user, err = txStore.GetUserByID(user.ID)
|
||||||
|
if err != nil {
|
||||||
|
_ = txStore.Rollback()
|
||||||
|
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if user.Disabled {
|
||||||
|
_ = txStore.Rollback()
|
||||||
|
WriteJSONWithErrorReason(w, r, http.StatusForbidden, "user disabled")
|
||||||
|
return
|
||||||
|
}
|
||||||
user.TOTPEffectiveRequired = required
|
user.TOTPEffectiveRequired = required
|
||||||
user.TOTPSetupRequired = required && !user.TOTPEnabled
|
user.TOTPSetupRequired = required && !user.TOTPEnabled
|
||||||
ok, err = api.verifyUserTOTPForLogin(r, user, otpCode)
|
ok, err = api.verifyUserTOTPForLoginWithStore(txStore, user, otpCode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_ = txStore.Rollback()
|
||||||
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
|
_ = txStore.Rollback()
|
||||||
if strings.TrimSpace(otpCode) == "" {
|
if strings.TrimSpace(otpCode) == "" {
|
||||||
WriteJSON(w, http.StatusUnauthorized, map[string]any{"error": "totp_required", "totp_required": true})
|
WriteJSON(w, http.StatusUnauthorized, map[string]any{"error": "totp_required", "totp_required": true})
|
||||||
return
|
return
|
||||||
@@ -61,19 +85,31 @@ func (api *API) finishLogin(w http.ResponseWriter, r *http.Request, user models.
|
|||||||
if required && !user.TOTPEnabled {
|
if required && !user.TOTPEnabled {
|
||||||
sessionVerified = false
|
sessionVerified = false
|
||||||
}
|
}
|
||||||
err = api.issueSession(w, r, user, sessionVerified, "")
|
token, expires, err = api.createSessionWithStore(txStore, user, sessionVerified, "")
|
||||||
|
if err != nil {
|
||||||
|
_ = txStore.Rollback()
|
||||||
|
api.Logger.Write(logIDAuth, codit_logger.LOG_WARN, "session create failed user=%s err=%v", user.Username, err)
|
||||||
|
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = txStore.Commit()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
api.setSessionCookie(w, token, expires)
|
||||||
WriteJSON(w, http.StatusOK, user)
|
WriteJSON(w, http.StatusOK, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) verifyUserTOTPForLogin(r *http.Request, user models.User, otpCode string) (bool, error) {
|
func (api *API) verifyUserTOTPForLogin(r *http.Request, user models.User, otpCode string) (bool, error) {
|
||||||
|
return api.verifyUserTOTPForLoginWithStore(api.store(r), user, otpCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) verifyUserTOTPForLoginWithStore(store *db.Store, user models.User, otpCode string) (bool, error) {
|
||||||
var item db.UserTOTP
|
var item db.UserTOTP
|
||||||
var secret string
|
var secret string
|
||||||
var err error
|
var err error
|
||||||
item, err = api.store(r).GetUserTOTP(user.ID)
|
item, err = store.GetUserTOTP(user.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return true, nil
|
return true, nil
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
const RepoTypeGit string = "git"
|
||||||
|
const RepoTypeRPM string = "rpm"
|
||||||
|
const RepoTypeDocker string = "docker"
|
||||||
|
|
||||||
|
const RoleAdmin string = "admin"
|
||||||
|
const RoleWriter string = "writer"
|
||||||
|
const RoleViewer string = "viewer"
|
||||||
|
|
||||||
|
const AuthProviderTypeDB string = "db"
|
||||||
|
const AuthProviderTypeLDAP string = "ldap"
|
||||||
|
const AuthProviderTypeOIDC string = "oidc"
|
||||||
|
|
||||||
|
const GroupSyncModeOff string = "off"
|
||||||
|
const GroupSyncModeFirstLogin string = "first_login"
|
||||||
|
const GroupSyncModeSync string = "sync"
|
||||||
|
|
||||||
|
const UserGroupScopeExplicit string = "explicit"
|
||||||
|
const UserGroupScopeAllUsers string = "all_users"
|
||||||
|
|
||||||
|
const RPMRepoModeLocal string = "local"
|
||||||
|
const RPMRepoModeMirror string = "mirror"
|
||||||
@@ -28,6 +28,8 @@ CREATE TABLE blocks (
|
|||||||
type TEXT NOT NULL,
|
type TEXT NOT NULL,
|
||||||
title TEXT NOT NULL DEFAULT '',
|
title TEXT NOT NULL DEFAULT '',
|
||||||
fields TEXT NOT NULL DEFAULT '{}',
|
fields TEXT NOT NULL DEFAULT '{}',
|
||||||
|
completed_at INTEGER NOT NULL DEFAULT 0,
|
||||||
|
completed_by INTEGER,
|
||||||
created_at INTEGER NOT NULL,
|
created_at INTEGER NOT NULL,
|
||||||
updated_at INTEGER NOT NULL,
|
updated_at INTEGER NOT NULL,
|
||||||
delete_at INTEGER NOT NULL DEFAULT 0,
|
delete_at INTEGER NOT NULL DEFAULT 0,
|
||||||
@@ -47,6 +49,8 @@ CREATE TABLE blocks_history (
|
|||||||
type TEXT NOT NULL,
|
type TEXT NOT NULL,
|
||||||
title TEXT NOT NULL DEFAULT '',
|
title TEXT NOT NULL DEFAULT '',
|
||||||
fields TEXT NOT NULL DEFAULT '{}',
|
fields TEXT NOT NULL DEFAULT '{}',
|
||||||
|
completed_at INTEGER NOT NULL DEFAULT 0,
|
||||||
|
completed_by INTEGER,
|
||||||
created_at INTEGER NOT NULL,
|
created_at INTEGER NOT NULL,
|
||||||
updated_at INTEGER NOT NULL,
|
updated_at INTEGER NOT NULL,
|
||||||
delete_at INTEGER NOT NULL DEFAULT 0,
|
delete_at INTEGER NOT NULL DEFAULT 0,
|
||||||
@@ -56,16 +60,141 @@ CREATE TABLE blocks_history (
|
|||||||
CREATE TABLE board_members (
|
CREATE TABLE board_members (
|
||||||
board_id INTEGER NOT NULL,
|
board_id INTEGER NOT NULL,
|
||||||
user_id INTEGER NOT NULL,
|
user_id INTEGER NOT NULL,
|
||||||
role TEXT NOT NULL DEFAULT 'editor',
|
role TEXT NOT NULL DEFAULT 'writer',
|
||||||
created_at INTEGER NOT NULL,
|
created_at INTEGER NOT NULL,
|
||||||
PRIMARY KEY (board_id, user_id),
|
PRIMARY KEY (board_id, user_id),
|
||||||
FOREIGN KEY (board_id) REFERENCES boards(id) ON DELETE CASCADE,
|
FOREIGN KEY (board_id) REFERENCES boards(id) ON DELETE CASCADE,
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE block_comments (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
public_id TEXT NOT NULL UNIQUE,
|
||||||
|
block_id INTEGER NOT NULL,
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
created_by INTEGER NOT NULL,
|
||||||
|
created_at INTEGER NOT NULL,
|
||||||
|
updated_at INTEGER NOT NULL,
|
||||||
|
delete_at INTEGER NOT NULL DEFAULT 0,
|
||||||
|
FOREIGN KEY (block_id) REFERENCES blocks(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (created_by) REFERENCES users(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE block_properties (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
block_id INTEGER NOT NULL UNIQUE,
|
||||||
|
status TEXT NOT NULL DEFAULT '',
|
||||||
|
card_type TEXT NOT NULL DEFAULT '',
|
||||||
|
priority TEXT NOT NULL DEFAULT '',
|
||||||
|
due_date TEXT NOT NULL DEFAULT '',
|
||||||
|
assignee_id INTEGER,
|
||||||
|
sprint TEXT NOT NULL DEFAULT '',
|
||||||
|
description TEXT NOT NULL DEFAULT '',
|
||||||
|
FOREIGN KEY (block_id) REFERENCES blocks(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (assignee_id) REFERENCES users(id) ON DELETE SET NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE board_field_values (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
public_id TEXT NOT NULL UNIQUE,
|
||||||
|
board_id INTEGER NOT NULL REFERENCES boards(id) ON DELETE CASCADE,
|
||||||
|
field TEXT NOT NULL,
|
||||||
|
value TEXT NOT NULL,
|
||||||
|
label TEXT NOT NULL,
|
||||||
|
color TEXT NOT NULL DEFAULT '',
|
||||||
|
display_order INTEGER NOT NULL DEFAULT 0,
|
||||||
|
start_date TEXT NOT NULL DEFAULT '',
|
||||||
|
end_date TEXT NOT NULL DEFAULT '',
|
||||||
|
created_at INTEGER NOT NULL,
|
||||||
|
updated_at INTEGER NOT NULL,
|
||||||
|
UNIQUE(board_id, field, value)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE block_checklist_items (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
public_id TEXT NOT NULL UNIQUE,
|
||||||
|
block_id INTEGER NOT NULL,
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
done INTEGER NOT NULL DEFAULT 0,
|
||||||
|
display_order INTEGER NOT NULL DEFAULT 0,
|
||||||
|
created_by INTEGER NOT NULL,
|
||||||
|
updated_by INTEGER NOT NULL,
|
||||||
|
created_at INTEGER NOT NULL,
|
||||||
|
updated_at INTEGER NOT NULL,
|
||||||
|
delete_at INTEGER NOT NULL DEFAULT 0,
|
||||||
|
FOREIGN KEY (block_id) REFERENCES blocks(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (created_by) REFERENCES users(id),
|
||||||
|
FOREIGN KEY (updated_by) REFERENCES users(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE block_attachments (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
public_id TEXT NOT NULL UNIQUE,
|
||||||
|
block_id INTEGER NOT NULL,
|
||||||
|
filename TEXT NOT NULL,
|
||||||
|
content_type TEXT NOT NULL DEFAULT 'application/octet-stream',
|
||||||
|
size INTEGER NOT NULL DEFAULT 0,
|
||||||
|
storage_path TEXT NOT NULL,
|
||||||
|
created_by INTEGER NOT NULL,
|
||||||
|
created_at INTEGER NOT NULL,
|
||||||
|
delete_at INTEGER NOT NULL DEFAULT 0,
|
||||||
|
FOREIGN KEY (block_id) REFERENCES blocks(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (created_by) REFERENCES users(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE block_assignees (
|
||||||
|
block_id INTEGER NOT NULL,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
assigned_at INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (block_id, user_id),
|
||||||
|
FOREIGN KEY (block_id) REFERENCES blocks(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS auth_provider_group_mappings (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
auth_provider_id INTEGER NOT NULL,
|
||||||
|
group_id INTEGER NOT NULL,
|
||||||
|
claim_value TEXT NOT NULL,
|
||||||
|
created_at INTEGER NOT NULL DEFAULT 0,
|
||||||
|
updated_at INTEGER NOT NULL DEFAULT 0,
|
||||||
|
FOREIGN KEY (auth_provider_id) REFERENCES auth_providers(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (group_id) REFERENCES user_groups(id) ON DELETE CASCADE,
|
||||||
|
UNIQUE (auth_provider_id, claim_value, group_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE repos ADD COLUMN description TEXT NOT NULL DEFAULT '';
|
||||||
|
ALTER TABLE users ADD COLUMN avatar_storage_path TEXT NOT NULL DEFAULT '';
|
||||||
|
ALTER TABLE users ADD COLUMN avatar_content_type TEXT NOT NULL DEFAULT '';
|
||||||
|
ALTER TABLE users ADD COLUMN avatar_updated_at INTEGER NOT NULL DEFAULT 0;
|
||||||
|
ALTER TABLE auth_providers ADD COLUMN oidc_end_session_url TEXT NOT NULL DEFAULT '';
|
||||||
|
ALTER TABLE auth_providers ADD COLUMN group_sync_mode TEXT NOT NULL DEFAULT 'off';
|
||||||
|
ALTER TABLE auth_providers ADD COLUMN oidc_post_logout_redirect INTEGER NOT NULL DEFAULT 0;
|
||||||
|
ALTER TABLE sessions ADD COLUMN oidc_id_token TEXT NOT NULL DEFAULT '';
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO block_properties (block_id, status, description)
|
||||||
|
SELECT bl.id,
|
||||||
|
COALESCE(json_extract(bl.fields, '$.status'), ''),
|
||||||
|
COALESCE(json_extract(bl.fields, '$.description'), '')
|
||||||
|
FROM blocks bl
|
||||||
|
WHERE bl.type = 'card' AND bl.delete_at = 0;
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO block_assignees (block_id, user_id, assigned_at)
|
||||||
|
SELECT block_id, assignee_id, strftime('%s', 'now')
|
||||||
|
FROM block_properties
|
||||||
|
WHERE assignee_id IS NOT NULL;
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_boards_project ON boards(project_id, delete_at);
|
CREATE INDEX IF NOT EXISTS idx_boards_project ON boards(project_id, delete_at);
|
||||||
CREATE INDEX IF NOT EXISTS idx_blocks_board ON blocks(board_id, delete_at);
|
CREATE INDEX IF NOT EXISTS idx_blocks_board ON blocks(board_id, delete_at);
|
||||||
CREATE INDEX IF NOT EXISTS idx_blocks_parent ON blocks(parent_id);
|
CREATE INDEX IF NOT EXISTS idx_blocks_parent ON blocks(parent_id);
|
||||||
CREATE INDEX IF NOT EXISTS idx_blocks_type ON blocks(board_id, type, delete_at);
|
CREATE INDEX IF NOT EXISTS idx_blocks_type ON blocks(board_id, type, delete_at);
|
||||||
CREATE INDEX IF NOT EXISTS idx_blocks_history_block ON blocks_history(block_id, insert_at DESC);
|
CREATE INDEX IF NOT EXISTS idx_blocks_history_block ON blocks_history(block_id, insert_at DESC);
|
||||||
CREATE INDEX IF NOT EXISTS idx_board_members_user ON board_members(user_id);
|
CREATE INDEX IF NOT EXISTS idx_board_members_user ON board_members(user_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_block_comments_block ON block_comments(block_id, delete_at);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_block_properties_block ON block_properties(block_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_board_field_values_board_field ON board_field_values(board_id, field);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_block_checklist_items_block ON block_checklist_items(block_id, delete_at, display_order);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_block_attachments_block ON block_attachments(block_id, delete_at, created_at DESC);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_block_assignees_user ON block_assignees(user_id, block_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_auth_provider_group_mappings_provider ON auth_provider_group_mappings(auth_provider_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_auth_provider_group_mappings_claim ON auth_provider_group_mappings(auth_provider_id, claim_value);
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
CREATE TABLE block_comments (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
public_id TEXT NOT NULL UNIQUE,
|
|
||||||
block_id INTEGER NOT NULL,
|
|
||||||
content TEXT NOT NULL,
|
|
||||||
created_by INTEGER NOT NULL,
|
|
||||||
created_at INTEGER NOT NULL,
|
|
||||||
updated_at INTEGER NOT NULL,
|
|
||||||
delete_at INTEGER NOT NULL DEFAULT 0,
|
|
||||||
FOREIGN KEY (block_id) REFERENCES blocks(id) ON DELETE CASCADE,
|
|
||||||
FOREIGN KEY (created_by) REFERENCES users(id)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE block_properties (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
block_id INTEGER NOT NULL UNIQUE,
|
|
||||||
status TEXT NOT NULL DEFAULT '',
|
|
||||||
card_type TEXT NOT NULL DEFAULT '',
|
|
||||||
priority TEXT NOT NULL DEFAULT '',
|
|
||||||
due_date TEXT NOT NULL DEFAULT '',
|
|
||||||
assignee_id INTEGER,
|
|
||||||
sprint TEXT NOT NULL DEFAULT '',
|
|
||||||
description TEXT NOT NULL DEFAULT '',
|
|
||||||
FOREIGN KEY (block_id) REFERENCES blocks(id) ON DELETE CASCADE,
|
|
||||||
FOREIGN KEY (assignee_id) REFERENCES users(id) ON DELETE SET NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
INSERT OR IGNORE INTO block_properties (block_id, status, description)
|
|
||||||
SELECT bl.id,
|
|
||||||
COALESCE(json_extract(bl.fields, '$.status'), ''),
|
|
||||||
COALESCE(json_extract(bl.fields, '$.description'), '')
|
|
||||||
FROM blocks bl
|
|
||||||
WHERE bl.type = 'card' AND bl.delete_at = 0;
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_block_comments_block ON block_comments(block_id, delete_at);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_block_properties_block ON block_properties(block_id);
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
CREATE TABLE board_sprints (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
public_id TEXT NOT NULL UNIQUE,
|
|
||||||
board_id INTEGER NOT NULL,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
start_date TEXT NOT NULL DEFAULT '',
|
|
||||||
end_date TEXT NOT NULL DEFAULT '',
|
|
||||||
display_order INTEGER NOT NULL DEFAULT 0,
|
|
||||||
created_at INTEGER NOT NULL,
|
|
||||||
updated_at INTEGER NOT NULL,
|
|
||||||
delete_at INTEGER NOT NULL DEFAULT 0,
|
|
||||||
FOREIGN KEY (board_id) REFERENCES boards(id) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_board_sprints_board ON board_sprints(board_id, delete_at);
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
-- Migration 005: unified board_field_values (replaces board_sprints; adds custom status/type/priority)
|
|
||||||
|
|
||||||
CREATE TABLE board_field_values (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
public_id TEXT NOT NULL UNIQUE,
|
|
||||||
board_id INTEGER NOT NULL REFERENCES boards(id) ON DELETE CASCADE,
|
|
||||||
field TEXT NOT NULL,
|
|
||||||
value TEXT NOT NULL,
|
|
||||||
label TEXT NOT NULL,
|
|
||||||
color TEXT NOT NULL DEFAULT '',
|
|
||||||
display_order INTEGER NOT NULL DEFAULT 0,
|
|
||||||
start_date TEXT NOT NULL DEFAULT '',
|
|
||||||
end_date TEXT NOT NULL DEFAULT '',
|
|
||||||
created_at INTEGER NOT NULL,
|
|
||||||
updated_at INTEGER NOT NULL,
|
|
||||||
UNIQUE(board_id, field, value)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX idx_board_field_values_board_field ON board_field_values(board_id, field);
|
|
||||||
|
|
||||||
-- Seed default status values for every existing board
|
|
||||||
INSERT INTO board_field_values (public_id, board_id, field, value, label, color, display_order, created_at, updated_at)
|
|
||||||
SELECT lower(hex(randomblob(8))), b.id, 'status', 'todo', 'To Do', '#1976d2', 0, strftime('%s','now'), strftime('%s','now') FROM boards b WHERE b.delete_at = 0;
|
|
||||||
INSERT INTO board_field_values (public_id, board_id, field, value, label, color, display_order, created_at, updated_at)
|
|
||||||
SELECT lower(hex(randomblob(8))), b.id, 'status', 'in_progress','In Progress', '#f59e0b', 1, strftime('%s','now'), strftime('%s','now') FROM boards b WHERE b.delete_at = 0;
|
|
||||||
INSERT INTO board_field_values (public_id, board_id, field, value, label, color, display_order, created_at, updated_at)
|
|
||||||
SELECT lower(hex(randomblob(8))), b.id, 'status', 'done', 'Done', '#16a34a', 2, strftime('%s','now'), strftime('%s','now') FROM boards b WHERE b.delete_at = 0;
|
|
||||||
|
|
||||||
-- Seed default type values
|
|
||||||
INSERT INTO board_field_values (public_id, board_id, field, value, label, color, display_order, created_at, updated_at)
|
|
||||||
SELECT lower(hex(randomblob(8))), b.id, 'type', 'task', 'Task', '#1976d2', 0, strftime('%s','now'), strftime('%s','now') FROM boards b WHERE b.delete_at = 0;
|
|
||||||
INSERT INTO board_field_values (public_id, board_id, field, value, label, color, display_order, created_at, updated_at)
|
|
||||||
SELECT lower(hex(randomblob(8))), b.id, 'type', 'bug', 'Bug', '#d32f2f', 1, strftime('%s','now'), strftime('%s','now') FROM boards b WHERE b.delete_at = 0;
|
|
||||||
INSERT INTO board_field_values (public_id, board_id, field, value, label, color, display_order, created_at, updated_at)
|
|
||||||
SELECT lower(hex(randomblob(8))), b.id, 'type', 'story', 'Story', '#388e3c', 2, strftime('%s','now'), strftime('%s','now') FROM boards b WHERE b.delete_at = 0;
|
|
||||||
INSERT INTO board_field_values (public_id, board_id, field, value, label, color, display_order, created_at, updated_at)
|
|
||||||
SELECT lower(hex(randomblob(8))), b.id, 'type', 'epic', 'Epic', '#7b1fa2', 3, strftime('%s','now'), strftime('%s','now') FROM boards b WHERE b.delete_at = 0;
|
|
||||||
|
|
||||||
-- Seed default priority values
|
|
||||||
INSERT INTO board_field_values (public_id, board_id, field, value, label, color, display_order, created_at, updated_at)
|
|
||||||
SELECT lower(hex(randomblob(8))), b.id, 'priority', 'low', 'Low', '#4caf50', 0, strftime('%s','now'), strftime('%s','now') FROM boards b WHERE b.delete_at = 0;
|
|
||||||
INSERT INTO board_field_values (public_id, board_id, field, value, label, color, display_order, created_at, updated_at)
|
|
||||||
SELECT lower(hex(randomblob(8))), b.id, 'priority', 'medium', 'Medium', '#2196f3', 1, strftime('%s','now'), strftime('%s','now') FROM boards b WHERE b.delete_at = 0;
|
|
||||||
INSERT INTO board_field_values (public_id, board_id, field, value, label, color, display_order, created_at, updated_at)
|
|
||||||
SELECT lower(hex(randomblob(8))), b.id, 'priority', 'high', 'High', '#ff9800', 2, strftime('%s','now'), strftime('%s','now') FROM boards b WHERE b.delete_at = 0;
|
|
||||||
INSERT INTO board_field_values (public_id, board_id, field, value, label, color, display_order, created_at, updated_at)
|
|
||||||
SELECT lower(hex(randomblob(8))), b.id, 'priority', 'urgent', 'Urgent', '#f44336', 3, strftime('%s','now'), strftime('%s','now') FROM boards b WHERE b.delete_at = 0;
|
|
||||||
|
|
||||||
-- Migrate existing board_sprints rows (value = public_id for rename safety)
|
|
||||||
INSERT INTO board_field_values (public_id, board_id, field, value, label, color, display_order, start_date, end_date, created_at, updated_at)
|
|
||||||
SELECT
|
|
||||||
s.public_id,
|
|
||||||
s.board_id,
|
|
||||||
'sprint',
|
|
||||||
s.public_id,
|
|
||||||
s.name,
|
|
||||||
'#7c3aed',
|
|
||||||
s.display_order,
|
|
||||||
COALESCE(s.start_date, ''),
|
|
||||||
COALESCE(s.end_date, ''),
|
|
||||||
s.created_at,
|
|
||||||
s.updated_at
|
|
||||||
FROM board_sprints s
|
|
||||||
WHERE s.delete_at = 0;
|
|
||||||
|
|
||||||
DROP TABLE board_sprints;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE repos ADD COLUMN description TEXT NOT NULL DEFAULT '';
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
ALTER TABLE blocks ADD COLUMN completed_at INTEGER NOT NULL DEFAULT 0;
|
|
||||||
ALTER TABLE blocks ADD COLUMN completed_by INTEGER;
|
|
||||||
ALTER TABLE blocks_history ADD COLUMN completed_at INTEGER NOT NULL DEFAULT 0;
|
|
||||||
ALTER TABLE blocks_history ADD COLUMN completed_by INTEGER;
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
CREATE TABLE block_checklist_items (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
public_id TEXT NOT NULL UNIQUE,
|
|
||||||
block_id INTEGER NOT NULL,
|
|
||||||
title TEXT NOT NULL,
|
|
||||||
done INTEGER NOT NULL DEFAULT 0,
|
|
||||||
display_order INTEGER NOT NULL DEFAULT 0,
|
|
||||||
created_by INTEGER NOT NULL,
|
|
||||||
updated_by INTEGER NOT NULL,
|
|
||||||
created_at INTEGER NOT NULL,
|
|
||||||
updated_at INTEGER NOT NULL,
|
|
||||||
delete_at INTEGER NOT NULL DEFAULT 0,
|
|
||||||
FOREIGN KEY (block_id) REFERENCES blocks(id) ON DELETE CASCADE,
|
|
||||||
FOREIGN KEY (created_by) REFERENCES users(id),
|
|
||||||
FOREIGN KEY (updated_by) REFERENCES users(id)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX idx_block_checklist_items_block ON block_checklist_items(block_id, delete_at, display_order);
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
CREATE TABLE block_attachments (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
public_id TEXT NOT NULL UNIQUE,
|
|
||||||
block_id INTEGER NOT NULL,
|
|
||||||
filename TEXT NOT NULL,
|
|
||||||
content_type TEXT NOT NULL DEFAULT 'application/octet-stream',
|
|
||||||
size INTEGER NOT NULL DEFAULT 0,
|
|
||||||
storage_path TEXT NOT NULL,
|
|
||||||
created_by INTEGER NOT NULL,
|
|
||||||
created_at INTEGER NOT NULL,
|
|
||||||
delete_at INTEGER NOT NULL DEFAULT 0,
|
|
||||||
FOREIGN KEY (block_id) REFERENCES blocks(id) ON DELETE CASCADE,
|
|
||||||
FOREIGN KEY (created_by) REFERENCES users(id)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX idx_block_attachments_block ON block_attachments(block_id, delete_at, created_at DESC);
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
CREATE TABLE block_assignees (
|
|
||||||
block_id INTEGER NOT NULL,
|
|
||||||
user_id INTEGER NOT NULL,
|
|
||||||
assigned_at INTEGER NOT NULL DEFAULT 0,
|
|
||||||
PRIMARY KEY (block_id, user_id),
|
|
||||||
FOREIGN KEY (block_id) REFERENCES blocks(id) ON DELETE CASCADE,
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
|
|
||||||
INSERT OR IGNORE INTO block_assignees (block_id, user_id, assigned_at)
|
|
||||||
SELECT block_id, assignee_id, strftime('%s', 'now')
|
|
||||||
FROM block_properties
|
|
||||||
WHERE assignee_id IS NOT NULL;
|
|
||||||
|
|
||||||
CREATE INDEX idx_block_assignees_user ON block_assignees(user_id, block_id);
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
ALTER TABLE users ADD COLUMN avatar_storage_path TEXT NOT NULL DEFAULT '';
|
|
||||||
ALTER TABLE users ADD COLUMN avatar_content_type TEXT NOT NULL DEFAULT '';
|
|
||||||
ALTER TABLE users ADD COLUMN avatar_updated_at INTEGER NOT NULL DEFAULT 0;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE auth_providers ADD COLUMN oidc_end_session_url TEXT NOT NULL DEFAULT '';
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
ALTER TABLE auth_providers ADD COLUMN group_sync_mode TEXT NOT NULL DEFAULT 'off';
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS auth_provider_group_mappings (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
auth_provider_id INTEGER NOT NULL,
|
|
||||||
group_id INTEGER NOT NULL,
|
|
||||||
claim_value TEXT NOT NULL,
|
|
||||||
created_at INTEGER NOT NULL DEFAULT 0,
|
|
||||||
updated_at INTEGER NOT NULL DEFAULT 0,
|
|
||||||
FOREIGN KEY (auth_provider_id) REFERENCES auth_providers(id) ON DELETE CASCADE,
|
|
||||||
FOREIGN KEY (group_id) REFERENCES user_groups(id) ON DELETE CASCADE,
|
|
||||||
UNIQUE (auth_provider_id, claim_value, group_id)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_auth_provider_group_mappings_provider ON auth_provider_group_mappings(auth_provider_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_auth_provider_group_mappings_claim ON auth_provider_group_mappings(auth_provider_id, claim_value);
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE auth_providers ADD COLUMN oidc_post_logout_redirect INTEGER NOT NULL DEFAULT 0;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE sessions ADD COLUMN oidc_id_token TEXT NOT NULL DEFAULT '';
|
|
||||||
+42
-42
@@ -745,11 +745,11 @@ func (app *Server) initHandlers() (*handlers.API, *http.ServeMux, error) {
|
|||||||
|
|
||||||
router.Handle("GET", "/api/health", api.Health)
|
router.Handle("GET", "/api/health", api.Health)
|
||||||
router.Handle("GET", "/api/app-info", txRead(api.AppInfo))
|
router.Handle("GET", "/api/app-info", txRead(api.AppInfo))
|
||||||
router.Handle("POST", "/api/login", txWrite(api.Login))
|
router.Handle("POST", "/api/login", api.Login)
|
||||||
router.Handle("POST", "/api/logout", txWrite(api.Logout))
|
router.Handle("POST", "/api/logout", txWrite(api.Logout))
|
||||||
router.Handle("GET", "/api/auth/providers", txRead(api.ListPublicAuthProviders))
|
router.Handle("GET", "/api/auth/providers", txRead(api.ListPublicAuthProviders))
|
||||||
router.Handle("GET", "/api/auth/oidc/:id/login", txRead(api.OIDCLoginWithProvider))
|
router.Handle("GET", "/api/auth/oidc/:id/login", txRead(api.OIDCLoginWithProvider))
|
||||||
router.Handle("GET", "/api/auth/oidc/:id/callback", txRead(api.OIDCCallbackWithProvider))
|
router.Handle("GET", "/api/auth/oidc/:id/callback", api.OIDCCallbackWithProvider)
|
||||||
router.Handle("GET", "/api/me", txRead(api.Me))
|
router.Handle("GET", "/api/me", txRead(api.Me))
|
||||||
router.Handle("PATCH", "/api/me", txWrite(api.UpdateMe))
|
router.Handle("PATCH", "/api/me", txWrite(api.UpdateMe))
|
||||||
router.Handle("POST", "/api/me/avatar", txWrite(api.UploadMyAvatar))
|
router.Handle("POST", "/api/me/avatar", txWrite(api.UploadMyAvatar))
|
||||||
@@ -761,7 +761,7 @@ func (app *Server) initHandlers() (*handlers.API, *http.ServeMux, error) {
|
|||||||
router.Handle("POST", "/api/me/totp/disable", txWrite(api.DisableMyTOTP))
|
router.Handle("POST", "/api/me/totp/disable", txWrite(api.DisableMyTOTP))
|
||||||
|
|
||||||
router.Handle("GET", "/api/users", txRead(api.ListUsers))
|
router.Handle("GET", "/api/users", txRead(api.ListUsers))
|
||||||
router.Handle("GET", "/api/users/:id/avatar", txRead(api.GetUserAvatar))
|
router.Handle("GET", "/api/users/:id/avatar", api.GetUserAvatar)
|
||||||
router.Handle("POST", "/api/users", txWrite(api.CreateUser))
|
router.Handle("POST", "/api/users", txWrite(api.CreateUser))
|
||||||
router.Handle("PATCH", "/api/users/:id", txWrite(api.UpdateUser))
|
router.Handle("PATCH", "/api/users/:id", txWrite(api.UpdateUser))
|
||||||
router.Handle("DELETE", "/api/users/:id", txWrite(api.DeleteUser))
|
router.Handle("DELETE", "/api/users/:id", txWrite(api.DeleteUser))
|
||||||
@@ -818,7 +818,7 @@ func (app *Server) initHandlers() (*handlers.API, *http.ServeMux, error) {
|
|||||||
router.Handle("GET", "/api/admin/pki/cas", txRead(api.ListPKICAs))
|
router.Handle("GET", "/api/admin/pki/cas", txRead(api.ListPKICAs))
|
||||||
router.Handle("GET", "/api/admin/pki/cas/:id", txRead(api.GetPKICA))
|
router.Handle("GET", "/api/admin/pki/cas/:id", txRead(api.GetPKICA))
|
||||||
router.Handle("PATCH", "/api/admin/pki/cas/:id", txWrite(api.UpdatePKICA))
|
router.Handle("PATCH", "/api/admin/pki/cas/:id", txWrite(api.UpdatePKICA))
|
||||||
router.Handle("GET", "/api/admin/pki/cas/:id/bundle", txRead(api.DownloadPKICABundle))
|
router.Handle("GET", "/api/admin/pki/cas/:id/bundle", api.DownloadPKICABundle)
|
||||||
router.Handle("POST", "/api/admin/pki/cas/root", txWrite(api.CreatePKIRootCA))
|
router.Handle("POST", "/api/admin/pki/cas/root", txWrite(api.CreatePKIRootCA))
|
||||||
router.Handle("POST", "/api/admin/pki/cas/intermediate", txWrite(api.CreatePKIIntermediateCA))
|
router.Handle("POST", "/api/admin/pki/cas/intermediate", txWrite(api.CreatePKIIntermediateCA))
|
||||||
router.Handle("GET", "/api/admin/pki/cas/:id/crl", txRead(api.GetPKICRL))
|
router.Handle("GET", "/api/admin/pki/cas/:id/crl", txRead(api.GetPKICRL))
|
||||||
@@ -827,7 +827,7 @@ func (app *Server) initHandlers() (*handlers.API, *http.ServeMux, error) {
|
|||||||
router.Handle("GET", "/api/admin/pki/certs/:id", txRead(api.GetPKICert))
|
router.Handle("GET", "/api/admin/pki/certs/:id", txRead(api.GetPKICert))
|
||||||
router.Handle("GET", "/api/admin/pki/certs/:id/inspect", txRead(api.GetPKICertInspect))
|
router.Handle("GET", "/api/admin/pki/certs/:id/inspect", txRead(api.GetPKICertInspect))
|
||||||
router.Handle("POST", "/api/admin/pki/certs/:id/renew-acme", txWrite(api.RenewPKICertWithACME))
|
router.Handle("POST", "/api/admin/pki/certs/:id/renew-acme", txWrite(api.RenewPKICertWithACME))
|
||||||
router.Handle("GET", "/api/admin/pki/certs/:id/bundle", txRead(api.DownloadPKICertBundle))
|
router.Handle("GET", "/api/admin/pki/certs/:id/bundle", api.DownloadPKICertBundle)
|
||||||
router.Handle("POST", "/api/admin/pki/certs", txWrite(api.IssuePKICert))
|
router.Handle("POST", "/api/admin/pki/certs", txWrite(api.IssuePKICert))
|
||||||
router.Handle("POST", "/api/admin/pki/certs/import", txWrite(api.ImportPKICert))
|
router.Handle("POST", "/api/admin/pki/certs/import", txWrite(api.ImportPKICert))
|
||||||
router.Handle("POST", "/api/admin/pki/certs/:id/revoke", txWrite(api.RevokePKICert))
|
router.Handle("POST", "/api/admin/pki/certs/:id/revoke", txWrite(api.RevokePKICert))
|
||||||
@@ -842,7 +842,7 @@ func (app *Server) initHandlers() (*handlers.API, *http.ServeMux, error) {
|
|||||||
router.Handle("DELETE", "/api/admin/pki/acme/profiles/:id", txWrite(api.DeleteACMEProfile))
|
router.Handle("DELETE", "/api/admin/pki/acme/profiles/:id", txWrite(api.DeleteACMEProfile))
|
||||||
router.Handle("GET", "/api/admin/pki/acme/orders", txRead(api.ListACMEOrders))
|
router.Handle("GET", "/api/admin/pki/acme/orders", txRead(api.ListACMEOrders))
|
||||||
router.Handle("POST", "/api/admin/pki/acme/orders", txWrite(api.CreateACMEOrder))
|
router.Handle("POST", "/api/admin/pki/acme/orders", txWrite(api.CreateACMEOrder))
|
||||||
router.Handle("POST", "/api/admin/pki/acme/orders/:id/finalize", txWrite(api.FinalizeACMEOrder))
|
router.Handle("POST", "/api/admin/pki/acme/orders/:id/finalize", api.FinalizeACMEOrder)
|
||||||
router.Handle("DELETE", "/api/admin/pki/acme/orders/:id", txWrite(api.DeleteACMEOrder))
|
router.Handle("DELETE", "/api/admin/pki/acme/orders/:id", txWrite(api.DeleteACMEOrder))
|
||||||
router.Handle("GET", "/api/admin/rpm/mirrors", txRead(api.ListAdminRPMMirrors))
|
router.Handle("GET", "/api/admin/rpm/mirrors", txRead(api.ListAdminRPMMirrors))
|
||||||
router.Handle("GET", "/api/admin/ssh/user-cas", txRead(api.ListSSHUserCAs))
|
router.Handle("GET", "/api/admin/ssh/user-cas", txRead(api.ListSSHUserCAs))
|
||||||
@@ -867,7 +867,7 @@ func (app *Server) initHandlers() (*handlers.API, *http.ServeMux, error) {
|
|||||||
router.Handle("DELETE", "/api/admin/ssh/servers/:id", txWrite(api.DeleteSSHServerAdmin))
|
router.Handle("DELETE", "/api/admin/ssh/servers/:id", txWrite(api.DeleteSSHServerAdmin))
|
||||||
router.Handle("GET", "/api/admin/ssh/servers/:id/host-keys", txRead(api.ListSSHServerHostKeysAdmin))
|
router.Handle("GET", "/api/admin/ssh/servers/:id/host-keys", txRead(api.ListSSHServerHostKeysAdmin))
|
||||||
router.Handle("POST", "/api/admin/ssh/servers/:id/host-keys", txWrite(api.CreateSSHServerHostKeyAdmin))
|
router.Handle("POST", "/api/admin/ssh/servers/:id/host-keys", txWrite(api.CreateSSHServerHostKeyAdmin))
|
||||||
router.Handle("POST", "/api/admin/ssh/servers/:id/host-keys/discover", txWrite(api.DiscoverSSHServerHostKeyAdmin))
|
router.Handle("POST", "/api/admin/ssh/servers/:id/host-keys/discover", api.DiscoverSSHServerHostKeyAdmin)
|
||||||
router.Handle("DELETE", "/api/admin/ssh/servers/:id/host-keys/:hostKeyId", txWrite(api.DeleteSSHServerHostKeyAdmin))
|
router.Handle("DELETE", "/api/admin/ssh/servers/:id/host-keys/:hostKeyId", txWrite(api.DeleteSSHServerHostKeyAdmin))
|
||||||
router.Handle("GET", "/api/admin/ssh/server-groups", txRead(api.ListSSHServerGroupsAdmin))
|
router.Handle("GET", "/api/admin/ssh/server-groups", txRead(api.ListSSHServerGroupsAdmin))
|
||||||
router.Handle("POST", "/api/admin/ssh/server-groups", txWrite(api.CreateSSHServerGroupAdmin))
|
router.Handle("POST", "/api/admin/ssh/server-groups", txWrite(api.CreateSSHServerGroupAdmin))
|
||||||
@@ -887,7 +887,7 @@ func (app *Server) initHandlers() (*handlers.API, *http.ServeMux, error) {
|
|||||||
router.Handle("DELETE", "/api/admin/ssh/access-profiles/:id", txWrite(api.DeleteSSHAccessProfileAdmin))
|
router.Handle("DELETE", "/api/admin/ssh/access-profiles/:id", txWrite(api.DeleteSSHAccessProfileAdmin))
|
||||||
router.Handle("GET", "/api/admin/ssh/sessions", txRead(api.ListSSHSessionsAdmin))
|
router.Handle("GET", "/api/admin/ssh/sessions", txRead(api.ListSSHSessionsAdmin))
|
||||||
router.Handle("GET", "/api/admin/ssh/file-transfers", txRead(api.ListSSHFileTransfersAdmin))
|
router.Handle("GET", "/api/admin/ssh/file-transfers", txRead(api.ListSSHFileTransfersAdmin))
|
||||||
router.Handle("GET", "/api/admin/ssh/sessions/:id/transcript", txRead(api.GetSSHSessionTranscriptAdmin))
|
router.Handle("GET", "/api/admin/ssh/sessions/:id/transcript", api.GetSSHSessionTranscriptAdmin)
|
||||||
router.Handle("GET", "/api/ssh/user-cas", txRead(api.ListSSHUserCAsForSelf))
|
router.Handle("GET", "/api/ssh/user-cas", txRead(api.ListSSHUserCAsForSelf))
|
||||||
router.Handle("GET", "/api/ssh/user-cas/:id", txRead(api.GetSSHUserCAForSelf))
|
router.Handle("GET", "/api/ssh/user-cas/:id", txRead(api.GetSSHUserCAForSelf))
|
||||||
router.Handle("GET", "/api/ssh/user-cas/:id/public-key", txRead(api.DownloadSSHUserCAPublicKeyForSelf))
|
router.Handle("GET", "/api/ssh/user-cas/:id/public-key", txRead(api.DownloadSSHUserCAPublicKeyForSelf))
|
||||||
@@ -899,7 +899,7 @@ func (app *Server) initHandlers() (*handlers.API, *http.ServeMux, error) {
|
|||||||
router.Handle("DELETE", "/api/ssh/servers/:id", txWrite(api.DeleteSSHServerForSelf))
|
router.Handle("DELETE", "/api/ssh/servers/:id", txWrite(api.DeleteSSHServerForSelf))
|
||||||
router.Handle("GET", "/api/ssh/servers/:id/host-keys", txRead(api.ListSSHServerHostKeysForSelf))
|
router.Handle("GET", "/api/ssh/servers/:id/host-keys", txRead(api.ListSSHServerHostKeysForSelf))
|
||||||
router.Handle("POST", "/api/ssh/servers/:id/host-keys", txWrite(api.CreateSSHServerHostKeyForSelf))
|
router.Handle("POST", "/api/ssh/servers/:id/host-keys", txWrite(api.CreateSSHServerHostKeyForSelf))
|
||||||
router.Handle("POST", "/api/ssh/servers/:id/host-keys/discover", txWrite(api.DiscoverSSHServerHostKeyForSelf))
|
router.Handle("POST", "/api/ssh/servers/:id/host-keys/discover", api.DiscoverSSHServerHostKeyForSelf)
|
||||||
router.Handle("DELETE", "/api/ssh/servers/:id/host-keys/:hostKeyId", txWrite(api.DeleteSSHServerHostKeyForSelf))
|
router.Handle("DELETE", "/api/ssh/servers/:id/host-keys/:hostKeyId", txWrite(api.DeleteSSHServerHostKeyForSelf))
|
||||||
router.Handle("GET", "/api/ssh/credentials", txRead(api.ListSSHCredentialsForSelf))
|
router.Handle("GET", "/api/ssh/credentials", txRead(api.ListSSHCredentialsForSelf))
|
||||||
router.Handle("POST", "/api/ssh/credentials", txWrite(api.CreateSSHCredentialForSelf))
|
router.Handle("POST", "/api/ssh/credentials", txWrite(api.CreateSSHCredentialForSelf))
|
||||||
@@ -921,12 +921,12 @@ func (app *Server) initHandlers() (*handlers.API, *http.ServeMux, error) {
|
|||||||
router.Handle("GET", "/api/ssh/file-transfers", txRead(api.ListSSHFileTransfersForSelf))
|
router.Handle("GET", "/api/ssh/file-transfers", txRead(api.ListSSHFileTransfersForSelf))
|
||||||
router.Handle("GET", "/api/ssh/stream", txRead(api.StreamSSHWorkspaceForSelf))
|
router.Handle("GET", "/api/ssh/stream", txRead(api.StreamSSHWorkspaceForSelf))
|
||||||
router.Handle("GET", "/api/ssh/sessions/:id", txRead(api.GetSSHSessionForSelf))
|
router.Handle("GET", "/api/ssh/sessions/:id", txRead(api.GetSSHSessionForSelf))
|
||||||
router.Handle("GET", "/api/ssh/sessions/:id/transcript", txRead(api.GetSSHSessionTranscriptForSelf))
|
router.Handle("GET", "/api/ssh/sessions/:id/transcript", api.GetSSHSessionTranscriptForSelf)
|
||||||
router.Handle("POST", "/api/ssh/sessions/:id/disconnect", txWrite(api.DisconnectSSHSessionForSelf))
|
router.Handle("POST", "/api/ssh/sessions/:id/disconnect", txWrite(api.DisconnectSSHSessionForSelf))
|
||||||
router.Handle("POST", "/api/ssh/sessions/:id/files/upload", txWrite(api.UploadSSHSessionFilesForSelf))
|
router.Handle("POST", "/api/ssh/sessions/:id/files/upload", api.UploadSSHSessionFilesForSelf)
|
||||||
router.Handle("POST", "/api/ssh/sessions/:id/files/download", txWrite(api.DownloadSSHSessionFilesForSelf))
|
router.Handle("POST", "/api/ssh/sessions/:id/files/download", api.DownloadSSHSessionFilesForSelf)
|
||||||
router.Handle("POST", "/api/ssh/sessions/:id/files/copy-to", txWrite(api.CopySSHSessionFilesForSelf))
|
router.Handle("POST", "/api/ssh/sessions/:id/files/copy-to", api.CopySSHSessionFilesForSelf)
|
||||||
router.Handle("POST", "/api/ssh/cert/inspect", txWrite(api.InspectSSHCertificate))
|
router.Handle("POST", "/api/ssh/cert/inspect", api.InspectSSHCertificate)
|
||||||
router.Handle("POST", "/api/ssh/user-cas/:id/sign", txWrite(api.SignSSHUserKeyForSelf))
|
router.Handle("POST", "/api/ssh/user-cas/:id/sign", txWrite(api.SignSSHUserKeyForSelf))
|
||||||
router.Handle("GET", "/api/ssh/issuances", txRead(api.ListSSHUserCAIssuancesForSelf))
|
router.Handle("GET", "/api/ssh/issuances", txRead(api.ListSSHUserCAIssuancesForSelf))
|
||||||
router.Handle("GET", "/api/pki/cas/:id/crl", txRead(api.GetPKICRLForSelf))
|
router.Handle("GET", "/api/pki/cas/:id/crl", txRead(api.GetPKICRLForSelf))
|
||||||
@@ -936,14 +936,14 @@ func (app *Server) initHandlers() (*handlers.API, *http.ServeMux, error) {
|
|||||||
router.Handle("POST", "/api/pki/client/certs", txWrite(api.IssuePKIClientCertForSelf))
|
router.Handle("POST", "/api/pki/client/certs", txWrite(api.IssuePKIClientCertForSelf))
|
||||||
router.Handle("GET", "/api/pki/client/certs/:id", txRead(api.GetPKIClientCertForSelf))
|
router.Handle("GET", "/api/pki/client/certs/:id", txRead(api.GetPKIClientCertForSelf))
|
||||||
router.Handle("GET", "/api/pki/client/certs/:id/inspect", txRead(api.GetPKIClientCertInspectForSelf))
|
router.Handle("GET", "/api/pki/client/certs/:id/inspect", txRead(api.GetPKIClientCertInspectForSelf))
|
||||||
router.Handle("GET", "/api/pki/client/certs/:id/bundle", txRead(api.DownloadPKIClientCertBundleForSelf))
|
router.Handle("GET", "/api/pki/client/certs/:id/bundle", api.DownloadPKIClientCertBundleForSelf)
|
||||||
router.Handle("POST", "/api/pki/client/certs/:id/revoke", txWrite(api.RevokePKIClientCertForSelf))
|
router.Handle("POST", "/api/pki/client/certs/:id/revoke", txWrite(api.RevokePKIClientCertForSelf))
|
||||||
|
|
||||||
router.Handle("GET", "/api/projects", txRead(api.ListProjects))
|
router.Handle("GET", "/api/projects", txRead(api.ListProjects))
|
||||||
router.Handle("POST", "/api/projects", txWrite(api.CreateProject))
|
router.Handle("POST", "/api/projects", txWrite(api.CreateProject))
|
||||||
router.Handle("GET", "/api/projects/:id", txRead(api.GetProject))
|
router.Handle("GET", "/api/projects/:id", txRead(api.GetProject))
|
||||||
router.Handle("PATCH", "/api/projects/:id", txWrite(api.UpdateProject))
|
router.Handle("PATCH", "/api/projects/:id", txWrite(api.UpdateProject))
|
||||||
router.Handle("DELETE", "/api/projects/:id", txWrite(api.DeleteProject))
|
router.Handle("DELETE", "/api/projects/:id", api.DeleteProject)
|
||||||
|
|
||||||
router.Handle("GET", "/api/projects/:projectId/members", txRead(api.ListProjectMembers))
|
router.Handle("GET", "/api/projects/:projectId/members", txRead(api.ListProjectMembers))
|
||||||
router.Handle("GET", "/api/projects/:projectId/member-candidates", txRead(api.ListProjectMemberCandidates))
|
router.Handle("GET", "/api/projects/:projectId/member-candidates", txRead(api.ListProjectMemberCandidates))
|
||||||
@@ -983,8 +983,8 @@ func (app *Server) initHandlers() (*handlers.API, *http.ServeMux, error) {
|
|||||||
router.Handle("PUT", "/api/boards/:boardId/blocks/:blockId/checklist/:itemId", txWrite(api.UpdateBlockChecklistItem))
|
router.Handle("PUT", "/api/boards/:boardId/blocks/:blockId/checklist/:itemId", txWrite(api.UpdateBlockChecklistItem))
|
||||||
router.Handle("DELETE", "/api/boards/:boardId/blocks/:blockId/checklist/:itemId", txWrite(api.DeleteBlockChecklistItem))
|
router.Handle("DELETE", "/api/boards/:boardId/blocks/:blockId/checklist/:itemId", txWrite(api.DeleteBlockChecklistItem))
|
||||||
router.Handle("GET", "/api/boards/:boardId/blocks/:blockId/attachments", txRead(api.ListBlockAttachments))
|
router.Handle("GET", "/api/boards/:boardId/blocks/:blockId/attachments", txRead(api.ListBlockAttachments))
|
||||||
router.Handle("POST", "/api/boards/:boardId/blocks/:blockId/attachments", txWrite(api.CreateBlockAttachment))
|
router.Handle("POST", "/api/boards/:boardId/blocks/:blockId/attachments", api.CreateBlockAttachment)
|
||||||
router.Handle("GET", "/api/boards/:boardId/blocks/:blockId/attachments/:attachmentId/download", txRead(api.DownloadBlockAttachment))
|
router.Handle("GET", "/api/boards/:boardId/blocks/:blockId/attachments/:attachmentId/download", api.DownloadBlockAttachment)
|
||||||
router.Handle("DELETE", "/api/boards/:boardId/blocks/:blockId/attachments/:attachmentId", txWrite(api.DeleteBlockAttachment))
|
router.Handle("DELETE", "/api/boards/:boardId/blocks/:blockId/attachments/:attachmentId", txWrite(api.DeleteBlockAttachment))
|
||||||
router.Handle("GET", "/api/boards/:boardId/blocks/:blockId/properties", txRead(api.GetBlockProperties))
|
router.Handle("GET", "/api/boards/:boardId/blocks/:blockId/properties", txRead(api.GetBlockProperties))
|
||||||
router.Handle("PUT", "/api/boards/:boardId/blocks/:blockId/properties", txWrite(api.UpsertBlockProperties))
|
router.Handle("PUT", "/api/boards/:boardId/blocks/:blockId/properties", txWrite(api.UpsertBlockProperties))
|
||||||
@@ -994,7 +994,7 @@ func (app *Server) initHandlers() (*handlers.API, *http.ServeMux, error) {
|
|||||||
router.Handle("DELETE", "/api/boards/:boardId/members/:userId", txWrite(api.RemoveBoardMember))
|
router.Handle("DELETE", "/api/boards/:boardId/members/:userId", txWrite(api.RemoveBoardMember))
|
||||||
|
|
||||||
router.Handle("GET", "/api/projects/:projectId/repos", txRead(api.ListRepos))
|
router.Handle("GET", "/api/projects/:projectId/repos", txRead(api.ListRepos))
|
||||||
router.Handle("POST", "/api/projects/:projectId/repos", txWrite(api.CreateRepo))
|
router.Handle("POST", "/api/projects/:projectId/repos", api.CreateRepo)
|
||||||
router.Handle("GET", "/api/repos/types", txRead(api.RepoTypes))
|
router.Handle("GET", "/api/repos/types", txRead(api.RepoTypes))
|
||||||
router.Handle("GET", "/api/repos", txRead(api.ListAllRepos))
|
router.Handle("GET", "/api/repos", txRead(api.ListAllRepos))
|
||||||
router.Handle("GET", "/api/projects/:projectId/foreign-repos/available", txRead(api.ListAvailableRepos))
|
router.Handle("GET", "/api/projects/:projectId/foreign-repos/available", txRead(api.ListAvailableRepos))
|
||||||
@@ -1002,23 +1002,23 @@ func (app *Server) initHandlers() (*handlers.API, *http.ServeMux, error) {
|
|||||||
router.Handle("DELETE", "/api/projects/:projectId/foreign-repos/:repoId", txWrite(api.DetachForeignRepo))
|
router.Handle("DELETE", "/api/projects/:projectId/foreign-repos/:repoId", txWrite(api.DetachForeignRepo))
|
||||||
router.Handle("GET", "/api/repos/:id", txRead(api.GetRepo))
|
router.Handle("GET", "/api/repos/:id", txRead(api.GetRepo))
|
||||||
router.Handle("PATCH", "/api/repos/:id", txWrite(api.UpdateRepo))
|
router.Handle("PATCH", "/api/repos/:id", txWrite(api.UpdateRepo))
|
||||||
router.Handle("DELETE", "/api/repos/:id", txWrite(api.DeleteRepo))
|
router.Handle("DELETE", "/api/repos/:id", api.DeleteRepo)
|
||||||
router.Handle("GET", "/api/repos/:id/branches", txRead(api.RepoBranches))
|
router.Handle("GET", "/api/repos/:id/branches", api.RepoBranches)
|
||||||
router.Handle("GET", "/api/repos/:id/branches/info", txRead(api.RepoBranchesInfo))
|
router.Handle("GET", "/api/repos/:id/branches/info", api.RepoBranchesInfo)
|
||||||
router.Handle("PUT", "/api/repos/:id/branches/default", txWrite(api.RepoSetDefaultBranch))
|
router.Handle("PUT", "/api/repos/:id/branches/default", api.RepoSetDefaultBranch)
|
||||||
router.Handle("POST", "/api/repos/:id/branches/rename", txWrite(api.RepoRenameBranch))
|
router.Handle("POST", "/api/repos/:id/branches/rename", api.RepoRenameBranch)
|
||||||
router.Handle("POST", "/api/repos/:id/branches/delete", txWrite(api.RepoDeleteBranch))
|
router.Handle("POST", "/api/repos/:id/branches/delete", api.RepoDeleteBranch)
|
||||||
router.Handle("POST", "/api/repos/:id/branches/create", txWrite(api.RepoCreateBranch))
|
router.Handle("POST", "/api/repos/:id/branches/create", api.RepoCreateBranch)
|
||||||
router.Handle("GET", "/api/repos/:id/commits", txRead(api.RepoCommits))
|
router.Handle("GET", "/api/repos/:id/commits", api.RepoCommits)
|
||||||
router.Handle("GET", "/api/repos/:id/tree", txRead(api.RepoTree))
|
router.Handle("GET", "/api/repos/:id/tree", api.RepoTree)
|
||||||
router.Handle("GET", "/api/repos/:id/blob", txRead(api.RepoBlob))
|
router.Handle("GET", "/api/repos/:id/blob", api.RepoBlob)
|
||||||
router.Handle("GET", "/api/repos/:id/blob/raw", txRead(api.RepoBlobRaw))
|
router.Handle("GET", "/api/repos/:id/blob/raw", api.RepoBlobRaw)
|
||||||
router.Handle("GET", "/api/repos/:id/history", txRead(api.RepoFileHistory))
|
router.Handle("GET", "/api/repos/:id/history", api.RepoFileHistory)
|
||||||
router.Handle("GET", "/api/repos/:id/diff", txRead(api.RepoFileDiff))
|
router.Handle("GET", "/api/repos/:id/diff", api.RepoFileDiff)
|
||||||
router.Handle("GET", "/api/repos/:id/commit", txRead(api.RepoCommitDetail))
|
router.Handle("GET", "/api/repos/:id/commit", api.RepoCommitDetail)
|
||||||
router.Handle("GET", "/api/repos/:id/commit/diff", txRead(api.RepoCommitDiff))
|
router.Handle("GET", "/api/repos/:id/commit/diff", api.RepoCommitDiff)
|
||||||
router.Handle("GET", "/api/repos/:id/compare", txRead(api.RepoCompare))
|
router.Handle("GET", "/api/repos/:id/compare", api.RepoCompare)
|
||||||
router.Handle("GET", "/api/repos/:id/stats", txRead(api.RepoStats))
|
router.Handle("GET", "/api/repos/:id/stats", api.RepoStats)
|
||||||
router.Handle("GET", "/api/repos/:id/rpm/packages", txRead(api.RepoRPMPackages))
|
router.Handle("GET", "/api/repos/:id/rpm/packages", txRead(api.RepoRPMPackages))
|
||||||
router.Handle("GET", "/api/repos/:id/rpm/package", txRead(api.RepoRPMPackage))
|
router.Handle("GET", "/api/repos/:id/rpm/package", txRead(api.RepoRPMPackage))
|
||||||
router.Handle("POST", "/api/repos/:id/rpm/subdirs", txWrite(api.RepoRPMCreateSubdir))
|
router.Handle("POST", "/api/repos/:id/rpm/subdirs", txWrite(api.RepoRPMCreateSubdir))
|
||||||
@@ -1028,15 +1028,15 @@ func (app *Server) initHandlers() (*handlers.API, *http.ServeMux, error) {
|
|||||||
router.Handle("POST", "/api/repos/:id/rpm/subdir/sync", txWrite(api.RepoRPMSyncSubdir))
|
router.Handle("POST", "/api/repos/:id/rpm/subdir/sync", txWrite(api.RepoRPMSyncSubdir))
|
||||||
router.Handle("POST", "/api/repos/:id/rpm/subdir/suspend", txWrite(api.RepoRPMSuspendSubdir))
|
router.Handle("POST", "/api/repos/:id/rpm/subdir/suspend", txWrite(api.RepoRPMSuspendSubdir))
|
||||||
router.Handle("POST", "/api/repos/:id/rpm/subdir/resume", txWrite(api.RepoRPMResumeSubdir))
|
router.Handle("POST", "/api/repos/:id/rpm/subdir/resume", txWrite(api.RepoRPMResumeSubdir))
|
||||||
router.Handle("POST", "/api/repos/:id/rpm/subdir/rebuild-metadata", txWrite(api.RepoRPMRebuildSubdirMetadata))
|
router.Handle("POST", "/api/repos/:id/rpm/subdir/rebuild-metadata", api.RepoRPMRebuildSubdirMetadata)
|
||||||
router.Handle("POST", "/api/repos/:id/rpm/subdir/cancel", txWrite(api.RepoRPMCancelSubdirSync))
|
router.Handle("POST", "/api/repos/:id/rpm/subdir/cancel", txWrite(api.RepoRPMCancelSubdirSync))
|
||||||
router.Handle("GET", "/api/repos/:id/rpm/subdir/runs", txRead(api.RepoRPMMirrorRuns))
|
router.Handle("GET", "/api/repos/:id/rpm/subdir/runs", txRead(api.RepoRPMMirrorRuns))
|
||||||
router.Handle("DELETE", "/api/repos/:id/rpm/subdir/runs", txWrite(api.RepoRPMClearMirrorRuns))
|
router.Handle("DELETE", "/api/repos/:id/rpm/subdir/runs", txWrite(api.RepoRPMClearMirrorRuns))
|
||||||
router.Handle("DELETE", "/api/repos/:id/rpm/subdir", txWrite(api.RepoRPMDeleteSubdir))
|
router.Handle("DELETE", "/api/repos/:id/rpm/subdir", txWrite(api.RepoRPMDeleteSubdir))
|
||||||
router.Handle("DELETE", "/api/repos/:id/rpm/file", txWrite(api.RepoRPMDeleteFile))
|
router.Handle("DELETE", "/api/repos/:id/rpm/file", txWrite(api.RepoRPMDeleteFile))
|
||||||
router.Handle("GET", "/api/repos/:id/rpm/file", txRead(api.RepoRPMFile))
|
router.Handle("GET", "/api/repos/:id/rpm/file", api.RepoRPMFile)
|
||||||
router.Handle("GET", "/api/repos/:id/rpm/tree", txRead(api.RepoRPMTree))
|
router.Handle("GET", "/api/repos/:id/rpm/tree", txRead(api.RepoRPMTree))
|
||||||
router.Handle("POST", "/api/repos/:id/rpm/upload", txWrite(api.RepoRPMUpload))
|
router.Handle("POST", "/api/repos/:id/rpm/upload", api.RepoRPMUpload)
|
||||||
router.Handle("GET", "/api/repos/:id/docker/images", txRead(api.RepoDockerImages))
|
router.Handle("GET", "/api/repos/:id/docker/images", txRead(api.RepoDockerImages))
|
||||||
router.Handle("GET", "/api/repos/:id/docker/tags", txRead(api.RepoDockerTags))
|
router.Handle("GET", "/api/repos/:id/docker/tags", txRead(api.RepoDockerTags))
|
||||||
router.Handle("GET", "/api/repos/:id/docker/manifest", txRead(api.RepoDockerManifest))
|
router.Handle("GET", "/api/repos/:id/docker/manifest", txRead(api.RepoDockerManifest))
|
||||||
@@ -1063,9 +1063,9 @@ func (app *Server) initHandlers() (*handlers.API, *http.ServeMux, error) {
|
|||||||
router.Handle("POST", "/api/projects/:projectId/wiki/pages", txWrite(api.CreateWikiPage))
|
router.Handle("POST", "/api/projects/:projectId/wiki/pages", txWrite(api.CreateWikiPage))
|
||||||
router.Handle("PATCH", "/api/wiki/pages/:id", txWrite(api.UpdateWikiPage))
|
router.Handle("PATCH", "/api/wiki/pages/:id", txWrite(api.UpdateWikiPage))
|
||||||
|
|
||||||
router.Handle("POST", "/api/projects/:projectId/uploads", txWrite(api.UploadFile))
|
router.Handle("POST", "/api/projects/:projectId/uploads", api.UploadFile)
|
||||||
router.Handle("GET", "/api/projects/:projectId/uploads", txRead(api.ListUploads))
|
router.Handle("GET", "/api/projects/:projectId/uploads", txRead(api.ListUploads))
|
||||||
router.Handle("GET", "/api/uploads/:id", txRead(api.DownloadFile))
|
router.Handle("GET", "/api/uploads/:id", api.DownloadFile)
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
@@ -1920,7 +1920,7 @@ func principalCanWriteService(service serviceKind, servicePrefix string, princip
|
|||||||
func roleAllowsWrite(role string) bool {
|
func roleAllowsWrite(role string) bool {
|
||||||
var r string
|
var r string
|
||||||
r = strings.ToLower(strings.TrimSpace(role))
|
r = strings.ToLower(strings.TrimSpace(role))
|
||||||
return r == "writer" || r == "admin"
|
return r == models.RoleWriter || r == models.RoleAdmin
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveProjectIDForServiceWrite(service serviceKind, servicePrefix string, r *http.Request, store *db.Store) (string, error) {
|
func resolveProjectIDForServiceWrite(service serviceKind, servicePrefix string, r *http.Request, store *db.Store) (string, error) {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ func createTestProject(t *testing.T, store *db.Store, user models.User, slug str
|
|||||||
Description: slug + " project",
|
Description: slug + " project",
|
||||||
CreatedBy: user.ID,
|
CreatedBy: user.ID,
|
||||||
UpdatedBy: user.ID,
|
UpdatedBy: user.ID,
|
||||||
HomePage: "info",
|
HomePage: "settings",
|
||||||
}
|
}
|
||||||
project, err = store.CreateProject(context.Background(), project)
|
project, err = store.CreateProject(context.Background(), project)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -458,7 +458,7 @@ Stores explicit per-board user sharing roles.
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `board_id` | Internal board FK. |
|
| `board_id` | Internal board FK. |
|
||||||
| `user_id` | Internal user FK. |
|
| `user_id` | Internal user FK. |
|
||||||
| `role` | Board role: `admin`, `editor`, or `viewer`. |
|
| `role` | Board role: `admin`, `writer`, or `viewer`. |
|
||||||
| `created_at` | Assignment timestamp. |
|
| `created_at` | Assignment timestamp. |
|
||||||
|
|
||||||
Primary key: `(board_id, user_id)`.
|
Primary key: `(board_id, user_id)`.
|
||||||
|
|||||||
+2
-2
@@ -11318,7 +11318,7 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
enum:
|
enum:
|
||||||
- admin
|
- admin
|
||||||
- editor
|
- writer
|
||||||
- viewer
|
- viewer
|
||||||
created_at:
|
created_at:
|
||||||
type: integer
|
type: integer
|
||||||
@@ -11333,7 +11333,7 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
enum:
|
enum:
|
||||||
- admin
|
- admin
|
||||||
- editor
|
- writer
|
||||||
- viewer
|
- viewer
|
||||||
additionalProperties: true
|
additionalProperties: true
|
||||||
RepositoryObject:
|
RepositoryObject:
|
||||||
|
|||||||
Reference in New Issue
Block a user