Жизненный цикл сессий устройства SafePass, версия 1
Вход по OPAQUE выдаёт JWT доступа на 15 минут и случайный 256-битный токен обновления. При передаче токен обновления кодируется в каноническом Base64URL без заполнения. PostgreSQL хранит только хеш SHA-256.
Каждый вход создаёт семейство сессии устройства. Запрос обновления погашает прежний токен и добавляет новый в одной транзакции. Повторное использование погашенного токена отзывает всё семейство. Текущий токен обновления истекает через 30 дней, а семейство — не позднее 90 дней с создания. Новый вход по OPAQUE создаёт новое семейство.
Flutter хранит токен обновления как двоичные данные в записи привязки аккаунта в SQLCipher. Ключ базы хранится в защищённом хранилище платформы. После разблокировки клиент обновляет токен и держит JWT доступа только в памяти. Токен также обновляется незадолго до истечения срока JWT. При блокировке клиент очищает буферы обоих токенов, а зашифрованный токен остаётся в базе до следующей разблокировки. Явный выход отзывает одно семейство, если API доступен. Аутентифицированный DELETE /v1/account/sessions отзывает все семейства аккаунта.
Идентификатор сессии и отзыв
JWT доступа содержит поле sid с идентификатором семейства в каноническом UUID нижнего регистра. При обновлении токена семейство и sid сохраняются до повторного входа. На каждом аутентифицированном запросе API проверяет принадлежность семейства аккаунту, отсутствие отзыва и 90-дневный срок. Выход, обнаружение повтора, смена пароля, восстановление аккаунта и удаление аккаунта прекращают действие соответствующих JWT сразу. Старые токены без sid принимаются только до истечения их срока.
Аутентифицированный GET /v1/account/sessions возвращает активные семейства вызывающего аккаунта по убыванию времени активности: id, createdAt (первый вход), lastActiveAt (последнее обновление), expiresAt (срок текущего токена обновления) и current (семейство вызывающего токена). Сервер не хранит для сессии имя устройства, User-Agent или IP. DELETE /v1/account/sessions/{id} отзывает одно семейство вызывающего аккаунта и возвращает 404 для неизвестного, чужого, просроченного или уже отозванного идентификатора.
Удаление аккаунта
POST /v1/account/deletion принимает тот же одноразовый двухминутный билет, что выдаётся при смене пароля через /v1/account/password-change/reauth/start и /finish. Удаление всегда требует свежего доказательства знания текущего пароля. Одна транзакция PostgreSQL погашает билет и удаляет зашифрованные ревизии аккаунта, курсор, вызовы восстановления, состояния входа OPAQUE и строку аккаунта. Токены обновления, конверт восстановления и другие билеты удаляются каскадно. Копии сейфа на устройствах остаются. Подписанные вызовы описаны в протоколе восстановления v1.
Обновление токена и выход возвращают общий отказ при ошибке аутентификации, используют тот же лимит на IP, что и маршруты OPAQUE, и не пишут тела запросов и токены в журналы. Если ответ об успешном обновлении потерялся, у клиента может остаться погашенный токен. При отклонении повторного запроса нужно войти снова.