Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Временные коды для приложений (oAuth для бедных) #9

Closed
fedor57 opened this issue Mar 8, 2017 · 20 comments · Fixed by #16
Closed
Assignees

Comments

@fedor57
Copy link
Owner

fedor57 commented Mar 8, 2017

В данных появляется новый вид кодов "временные коды для приложений". Эти коды можно использовать каждый только в своем приложении, с их помощью нельзя обращаться к другим, приглашать выпускников и пр.

Подробное описание на wiki:

https://github.com/fedor57/alumni-auth/wiki/TempAppCodesSpec

@alexshpilkin
Copy link
Collaborator

alexshpilkin commented Mar 8, 2017

Сценарий 1 можно переделать так, что доверять приложению не нужно (помимо выдачи ему идентификатора):

  1. Приложение, когда хочет залогинить пользователя, перенаправляет его на страницу на сервере auth с параметрами (〈ID приложения〉, 〈URL входа〉, 〈URL ошибки〉), URLы должны быть внутри домена приложения.
  2. Пользователь видит страницу на сайте auth, на которой написано либо «введите код, чтобы залогиниться в 〈приложение〉» (если он не залогинен в auth), либо «подтвердите, что вы хотите залогиниться в 〈приложение〉» (если залогинен в auth, но не приложение), либо переходит сразу в 3б (если уже логинился в приложение).
  3. (a) При отмене пользователь перенаправляется на 〈URL ошибки〉 без параметров. Приложение делает что хочет.
    (б) При подтверждении на auth создаётся временный код. Пользователь перенаправляется на 〈URL входа〉 с параметрами (〈данные о пользователе〉, 〈замаскированный исходный код〉, 〈полный временный код〉). Приложение использует временный код как обычный.

NB Что именно означают слова «уже логинился в приложение», надо уточнить [кажется, сценарий 2 оригинального предложения тоже этим грешит]. Например: понадобится ли генерить новый временный код при каждом слёте сессии? (Представить себе двух альтер эго пользователя, знающих с независимые наборы кодов.)

@alexshpilkin
Copy link
Collaborator

alexshpilkin commented Mar 8, 2017

Насколько я понимаю, сценарий 2 нужен только для поддержки legacy и недоверенных приложений. Второе при моём решении пропадает, первого у нас нет (ну почти), и сценарий 2 становится не нужен. Дополнительно UX-соображение: при отсутствии сценария 2 пользователю вообще не нужно знать, что временные коды — это коды, можно их показывать как-то по-другому (ср. Authorized apps в Твиттере, например)

@fedor57
Copy link
Owner Author

fedor57 commented Mar 8, 2017

Да, это больше похоже на честный oAuth, для каких-то приложений мы его сделаем, но не для всех. Какие есть соображения?

  1. Для Google Forms и внешних "готовых" приложений, которые сложно переделывать, нам доступна только верстка и javascript.

  2. Вариант с редиректом более болезненный для пользователя. Прозрачная серверная проверка немного комфортней.

Т.е., конечно, вариант, который я описал, - это не oAuth никакой. Он решает такую задачу:

  • есть приложение, авторам которого мы в целом доверяем
  • но мы не хотим разрешать ему хранить у себя оригинальные коды длительное время в базе. Какая-нибудь версия непременно утечет.

Чем-то это напоминает не oAuth, а процессинг кредитных карточек:

  • сайты имеют право принимать, но не имеют право хранить в базе номер карты рядом с cvc кодом, эта информация должна обрабатываться на лету через процессинговый центр
  • онлайн-банки выдают защищенные номера виртуальных кредиток с ограниченным лимитом по времени и по деньгам

Т.е. предлагаю пока сделать полузащищенную версию, а потом попланировать полноценную.

@alexshpilkin
Copy link
Collaborator

Это просто OAuth минус перманентные авторизации и криптография, именно. Остаётся всего ничего (и мне кажется, что реализация и «полузащищённого», и «честного» примерно одинаково сложна). Аргумент про болезненность не покупаю, все видели Гугл/Фейсбук/Твиттер и привыкли; либо ничего не видели, но тогда ручное копирование временных кодов — ещё большая жесть.

А какие именно требования у гуглоформ? Редирект через js-то сделать легко, а вот как их заставить принять ответные данные (что в сценарии 1, что в моём)? Хорошо бы это в ТЗ написать.

@alexshpilkin
Copy link
Collaborator

По модели данных:

  • Не вижу, зачем is_temporary_for, это вроде бы стандартный случай выдачи (is_issued_by) кода нестандартного типа.
  • Если поле disabled_at может указывать в будущее, надо пересмотреть, что означает статус: сейчас disabled_at != NULLstatus != OK (и это используется в коде); а с экспирацией уже не статус, а скорее причина отключения получается.

@fedor57
Copy link
Owner Author

fedor57 commented Mar 8, 2017

Чтобы лучше понимать условия задачи:

  1. Основное и самое массовое приложение - это Google Forms. Там можно сделать http-запрос и поиграть с вводимыми данными. Интерфейс ввода кода - это какой-то третий шаг в опросе. Утаскивать куда-то пользователя редиректами - это совершенно лишнее. Авторы приложения Google Forms ничего у нас воровать не станут, работают по https

  2. Большинство пользователей не будут заходить в alumni-auth. Они просто получат код через сообщение в Фейсбуке и пойдут сразу по ссылке в голосовалку.

  3. Воронки - это важно. Т.е. пользователям просто удобно ввести код и все. Они ничего не будут думать про какие-то логины, соотношения между сервисами, что там что-то есть в бекграунде происходит. Если мы начинаем им морочить голову, часть недовольны, часть отваливаются.

  4. Для тех, кто зашел на alumni-auth, полезно дать ссылки на приложения. Я тут это совместил определенным образом.

@alexshpilkin
Copy link
Collaborator

alexshpilkin commented Mar 8, 2017

Большинство пользователей не будут заходить в alumni-auth. Они просто получат код через сообщение в Фейсбуке и пойдут сразу по ссылке в голосовалку.

Да, поэтому я и предложил совместить вход в auth и авторизацию приложения.

@fedor57
Copy link
Owner Author

fedor57 commented Mar 8, 2017

Про is_temporary_for - я сначала думал вообще id-приложения положить в стрелку, чтобы его было проще искать. Ибо временный код - это "интерфейс" или "декоратор" к основному коду. Но потом понял, что в коде тоже нужно хранить приложение для ограничения и убрал. Наверное, подобный вид выписки означает какую-то определенную связь между кодами, которую можно учитывать в графе траста. Поэтому я бы оставил, чтобы не выводить косвенно эту информацию не из стрелок.

@fedor57
Copy link
Owner Author

fedor57 commented Mar 8, 2017

Я исхожу из того, что большинство проголосовавших пользователей будут "листовыми" в графе, т.е. они ни разу не зайдут в auth, не будут забивать себе голову смыслом этого приложения и пр.

Их сценарий простой:

  • получают код в сообщении
  • кликают по ссылке на голосование
  • попадают в более-менее понятную им гуглоформу
  • там спрашивают "у вас есть код?", говорят - "да"
  • введите код, ввели
  • дальше сразу голосование

Постарайся этот сценарий не сломать без какой-то понятной цели.

@mxposed
Copy link
Collaborator

mxposed commented Apr 13, 2017

По поводу модели данных: я не понимаю, зачем кодам expiry date. Что будет, если это время наступит?
Мне кажется, лучше в модели «приложение» завести статус «заархивировано» или флаг «прошло», и переставать выписывать временные коды по id такого приложения.

@fedor57
Copy link
Owner Author

fedor57 commented Apr 13, 2017

expire date - я думаю, что это ограничение на время использования в этом качестве, например, для голосования. Нужно, чтобы утекшие коды нельзя было использовать бесконечно в отсутствии мастер-кода. При повторном запросе кода того же типа мы возвращаем тот же код, даже, если срок действия истек, но увеличиваем время экспирации (максимум старого и запрошенного). Но для этого действия нужен "исходный" код.

Дальше, если мы использовали временный код, он записывается в табличку вместе с датой использования. При повторной валидации мы передаем флажок "был ли действителен на момент использования", а не сейчас. Если мы узнаем, что код был украден в начале февраля, а голосование было в середине, мы можем поставить дату экспирации в прошлое и тем самым инвалидировать использования кода в прошлом.

@fedor57
Copy link
Owner Author

fedor57 commented Apr 13, 2017

У нас есть "вечные" приложения типа директории. Но для кодов, выданных под эти приложения, нужна экспирация.

@mxposed
Copy link
Collaborator

mxposed commented Apr 13, 2017

Дальше, если мы использовали временный код, он записывается в табличку вместе с датой использования. При повторной валидации мы передаем флажок "был ли действителен на момент использования", а не сейчас.

Вот это совсем не понимаю пока : (

Про директорию тоже непонятно: во-первых, она наша тоже, во-вторых, правильно ли я понимаю, что ты хочешь переделать её на использование временных кодов? То есть пользователь первый раз вводит настоящий код, получает временный, и далее в директории используется всегда временный? Если пользователь отходил надолго, то ему опять нужно ввести настоящий, чтобы продлить врееменный дальше? Но сейчас мы не проверяем валидность кода на каждый заход в директорию — только на авторизацию

@fedor57
Copy link
Owner Author

fedor57 commented Apr 13, 2017

Голосование устроено так: в базе рядом с голосом сохраняется код, использованный при голосовании, а также дата-время использования. Дальше есть некое API валидации (пакетное), которое передает в auth код приложения и массив (код, время использования). Это API про каждый код отвечает, валиден ли он был на момент использования, а также какие-то характеристики доверия (уже текущие).

Что может произойти? Собрали голоса, посчитали. Дальше нашли аномалии, оттрейсили историю появления кода, поняли, что код угнали или неправильно выписали. Корректируем базу auth, указав, что код угнали, предположительно, такого-то числа. Дальше идет опять пакетный вызов, который про этот код, использованный такого-то числа, уже говорит "плохой". При этом, предыдущие использования с датой меньшей даты угона по-прежнему считаются хорошими.

@fedor57
Copy link
Owner Author

fedor57 commented Apr 13, 2017

С директорией - люди логинятся либо мастер-кодом, либо кодом для этого приложения, который они самостоятельно получают в auth.alumni57.ru. В первом случае скрипт или что-то автоматически по мастер-коду запрашивает код для приложения в момент логина. В директорию правки записываются с кодами для приложения. Во втором мы валидируем код через API, проверяя текущую валидность и то, что он разрешен для использования в этом приложении.

Дальше, в директории сохраняются "голосования" за правки, у каждого, опять же, дата и время использования. В результате перепроверки старые правки вандалов могут быть инвалидированы, как и для голосования.

@mxposed
Copy link
Collaborator

mxposed commented Apr 13, 2017

Ок, у нас есть временный код на 2 дня, что означает, что мы ему доверяем так же как исходному для всех действий в этом приложении в течение этих 2х дней. Дальше он заканчивается. Чтобы его продлить, директория должна об этом узнать. В целом решаемо, конечно.

Теперь если этот код стал известен кому-то ещё. Код временный, он не может им делать ничего в auth. Но внутри того же приложения он может делать что угодно. Как только настоящий пользователь перепродлит свой код, все прошлые действия ненастоящего пользователя станут валидными. Мне кажется, при такой схеме надо не продлять код, а выписывать новый

@mxposed
Copy link
Collaborator

mxposed commented Apr 13, 2017

Технически. Мы меняем оригинальный код на временный если:
— оригинальный код у нас есть
— он активный
— приложение у нас есть
и выставляем expiry в now + настройку приложения

Если для этого оригинального кода для этого приложения есть активный временный — мы его просто возвращаем

Если передан временный код и он от переданного приложения и активный — мы его просто возвращаем

Остальное — bad request

Так?

А зачем возвращать маскированный оригинальный? И в любых ответах его возвращать или нет?

@fedor57
Copy link
Owner Author

fedor57 commented Apr 13, 2017

Мы инвалидируем коды в прошлом, если явно поняли, что его угнали. Например, видим правку с кодом, а человек, кому он должен принадлежать, говорит, что правку не делал. Если код угнали, мы должны инвалидировать все действия с момента угона, а не сортировать сессии на хорошие и плохие.

Экспирация кода нужна, чтобы если человек перестал пользоваться приложением, но код утек задним числом, чтобы им нельзя было ничего сделать. Это просто уменьшает вероятность манипуляций, но не исключает их, конечно.

@fedor57
Copy link
Owner Author

fedor57 commented Apr 13, 2017

Если человек заново ввел мастер-код и мы генерируем временный, а приложение запрашивает 2 недели, мы должны обеспечить, чтобы временный код работал минимум 2 недели. Итого, если код уже был, нужно посчитать max(expiration-time, now + 2weeks) и записать его в expiration-time.

В остальном - все так.

@mxposed
Copy link
Collaborator

mxposed commented Apr 19, 2017

Выкатил

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants