вход переехал на доверенный заголовок Authelia вместо собственного OIDC
- пришедшего называет заголовок Remote-User от прокси, и верят ему только с адреса из перечня trusted_proxies; своего входа у сервиса не осталось — ни корня /auth, ни кук, ни срока сессии, ни секрета клиента в конфиге и в базе - учётная запись заводится первым обращением: EnsureUser в пакете хранилища, шаг схемы 202608220001 с колонкой provider_login и снятыми правилами users - cmd/oidcstub заменён на cmd/devtools с подкомандой proxy; заодно закрыт унаследованный DL3066 — пользователь образа назван числом
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
)
|
||||
|
||||
// ProviderLoginField — колонка, в которой лежит ключ учётной записи: логин
|
||||
// человека у провайдера, тот самый, которым его называет обратный прокси.
|
||||
//
|
||||
// Имя говорит о происхождении значения, а не о заголовке, которым оно приехало:
|
||||
// заголовок — способ доставки и может смениться, а логин у провайдера — то, чем
|
||||
// значение является. Колонка уезжает шагом схемы и потому не переименовывается.
|
||||
const ProviderLoginField = "provider_login"
|
||||
|
||||
// providerLoginIndex — имя уникального индекса по ключу учётной записи.
|
||||
const providerLoginIndex = "idx_users_provider_login"
|
||||
|
||||
// up202608220001 переводит узнавание пришедшего с протокола OIDC на логин,
|
||||
// который называет доверенный источник.
|
||||
//
|
||||
// Три части, и каждая закрывает своё.
|
||||
//
|
||||
// Первая — ключ учётной записи. Прежде идентичность человека лежала в системной
|
||||
// таблице внешних учётных записей библиотеки: её вела механика обмена кода, и
|
||||
// правил её только владелец панели. Механика уходит, и ключу нужен свой дом —
|
||||
// колонка с уникальным индексом. Почта ключом не годится: провайдер не обязан
|
||||
// её приносить, человек её меняет, а первое обращение с чужим адресом досталось
|
||||
// бы чужой записи.
|
||||
//
|
||||
// Вторая — необязательная почта. Умолчание библиотеки требует непустого адреса
|
||||
// у всякой учётной записи; заголовка с почтой может не быть вовсе, а
|
||||
// уникальность почты держится **частичным** индексом (`WHERE email != ”`),
|
||||
// поэтому записи без почты уживаются друг с другом. Пароль остаётся
|
||||
// обязательным при любом значении признака — ему ставится случайный, употребить
|
||||
// его нельзя: вход по паролю у коллекции выключен прежним шагом.
|
||||
//
|
||||
// Третья — поверхность коллекции пользователей. Умолчание библиотеки открывает
|
||||
// владельцу записи чтение, правку и удаление собственной строки, и до сих пор
|
||||
// это ничему не мешало ровно потому, что до поверхности хранилища браузер с
|
||||
// кукой не дотягивался: слой предъявления жил под корнем приложения. С
|
||||
// узнаванием по заголовку такая защита перестаёт быть защитой, а ключ учётной
|
||||
// записи лежит теперь обычной колонкой — то есть правка своей записи и есть
|
||||
// захват чужого имени: поставил себе ключом чужой логин, и первое обращение
|
||||
// настоящего его владельца попало бы в твою запись вместе со всем архивом.
|
||||
// Правила снимаются в пустое, что у хранилища означает «только владелец
|
||||
// панели»; наш код читает и заводит запись мимо правил, панель работает
|
||||
// суперпользователем, своих экранов профиля сервис не заводит.
|
||||
func up202608220001(app core.App) error {
|
||||
users, err := app.FindCollectionByNameOrId(UsersCollection)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find users collection: %w", err)
|
||||
}
|
||||
|
||||
users.Fields.Add(&core.TextField{
|
||||
Name: ProviderLoginField,
|
||||
// Предел тот же, что у имени в умолчании библиотеки: логин длиннее
|
||||
// имени не бывает, а колонка без предела принимала бы килобайты,
|
||||
// пришедшие заголовком.
|
||||
Max: 255,
|
||||
})
|
||||
// Индекс **частичный** — ровно как соседний индекс почты у той же коллекции.
|
||||
// Сплошной запретил бы вторую запись с пустым ключом, а такая заводится
|
||||
// рукой в панели и остаётся у всякой базы, пережившей прежний вход: подъём
|
||||
// на ней ронял бы накатку шага отказом уникальности, и сервис не стартовал
|
||||
// бы вовсе. Пустым ключом при этом не узнаётся никто — это держит приём
|
||||
// значения, а не индекс.
|
||||
users.AddIndex(providerLoginIndex, true, ProviderLoginField, ProviderLoginField+" != ''")
|
||||
|
||||
email, ok := users.Fields.GetByName(core.FieldNameEmail).(*core.EmailField)
|
||||
if !ok {
|
||||
return errors.New("users collection has no email field")
|
||||
}
|
||||
email.Required = false
|
||||
|
||||
// Механика OIDC снимается целиком: настройки провайдера больше не приводятся
|
||||
// к конфигу при подъёме, и обменивать код не на что.
|
||||
users.OAuth2.Enabled = false
|
||||
users.OAuth2.Providers = nil
|
||||
|
||||
// Наглухо все пять: заведение записи идёт нашим кодом, мимо правил.
|
||||
users.ListRule = nil
|
||||
users.ViewRule = nil
|
||||
users.CreateRule = nil
|
||||
users.UpdateRule = nil
|
||||
users.DeleteRule = nil
|
||||
|
||||
if err := app.Save(users); err != nil {
|
||||
return fmt.Errorf("failed to switch users collection to provider login: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// down202608220001 убирает ключ учётной записи и возвращает обязательность
|
||||
// почты.
|
||||
//
|
||||
// Правила доступа сюда не возвращаются намеренно — ни открытое создание записи,
|
||||
// которое закрывал прежний шаг, ни открытая правка, которую закрывает этот.
|
||||
// Откат, восстанавливающий их, оставил бы сервис хуже, чем он был: правка своей
|
||||
// записи открыта только тому, кто узнан, а узнают теперь по колонке, которую эта
|
||||
// правка и переписывает.
|
||||
func down202608220001(app core.App) error {
|
||||
users, err := app.FindCollectionByNameOrId(UsersCollection)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find users collection: %w", err)
|
||||
}
|
||||
|
||||
users.RemoveIndex(providerLoginIndex)
|
||||
users.Fields.RemoveByName(ProviderLoginField)
|
||||
|
||||
email, ok := users.Fields.GetByName(core.FieldNameEmail).(*core.EmailField)
|
||||
if !ok {
|
||||
return errors.New("users collection has no email field")
|
||||
}
|
||||
email.Required = true
|
||||
|
||||
if err := app.Save(users); err != nil {
|
||||
return fmt.Errorf("failed to restore users collection: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user