«phpClub» — архив тем ("тредов"), посвящённых изучению PHP и веб-технологий.
Crypter someApprentice 2019/02/25 14:14:56  №1354910 1
https://github.com/someApprentice/Crypter

Для начала реализовал сервис авторизации. Можно сказать, что это выполненная задача Студентов https://github.com/codedokode/pasta/blob/master/student-list.md выполненная на JavaScript, с Server Side Rendering и SPA, поэтому если новичкам интересно они могут что-то подсмотреть.

Если у новичков есть какие-то вопросы - чувствуйте себя свободно задавать их.


Всё сделано "как по учебнику" и интересного здесь мало чего, за исключением пару вещей.

1. При первом заходе на сайт, пользователя встречает форма Приветствия, в которую нужно заполнить email и, в зависимости от того зарегистрирован ли пользователь или нет, перенаправляет на страницу регистрации или логина, а соответствующий импут email'а автозаполняется.

Это реализовано с помощью пары строчек, которые добавляют для определенного роута поле data с этим email'ом:

https://github.com/someApprentice/Crypter/blob/master/src/app/auth/welcome/welcome.component.ts#L46-L49

https://github.com/someApprentice/Crypter/blob/master/src/app/auth/registration/registration.component.ts#L75-L77
https://github.com/someApprentice/Crypter/blob/master/src/app/auth/login/login.component.ts#L35-L37

Но получив некий опыт на Ангуляре, я понял что более шаблонным и следовательно простым для понимания было бы обернуть все эти компоненты в отдельный компонент AuthComponent и в нём уже сделать свойство email (а лучше объект user/form) и передавать это значение с помощью "Взаимодействий Компонентов" https://angular.io/guide/component-interaction.

2. Поскольку приложение пререндерится на сервере а так же работает в браузере, ему нужен общий "источник информации" о пользовательском хранилище. Так как, для сервера используются лучшие пользовательское хранилище это Кукисы по сути кукисы не на что не влияют кроме как условий отображения пререндеренной страницы, а для браузера localStorage, то для обоих этих хранилищ написан сервис-обёртка.

https://github.com/someApprentice/Crypter/blob/master/src/app/storage.service.ts#L18-L20
https://github.com/someApprentice/Crypter/blob/master/src/app/storage.service.ts#L22-L26

https://github.com/someApprentice/Crypter/blob/master/src/app/models/StorageWrapper.ts


Небольшие вопросы, которые не критичны, но всё же знать их следует чтобы довести до идеала:

https://github.com/someApprentice/Crypter/blob/master/api/api.ts#L31
Позволяет ли Bearer token защититься от XSRF?

Я детально изучил эту уязвимость и касаемо этого вопроса, критический момент зависит от того, что может ли злоумышленник "обмануть" браузер отправить автоматически запрос с этим токеном. Он может, например, сделать XMLHttpRequests с ним (со своего сайта), но для начала ему нужно узнать этот токен, что практически тоже самое что добавлять csrf-token, чтобы бороться с этой уязвимостью.

Способ с Bearer token'ом технически идентичен с методом Cookie-to-header token ( https://en.wikipedia.org/wiki/Cross-site_request_forgery#Cookie-to-header_token ) за исключением семантики заголовка X-Csrf-Token заменённого на Bearer token.
И в моём случае, токен хранится в Кукисах, которые защищены с помощью HttpOnly и Secure флагов и в localStorage.

Приходит ли вам на ум какая-нибудь слабая точка которую можно эксплуатировать?

https://github.com/someApprentice/Crypter/blob/master/src/app/storage.service.spec.ts#L32-L33
Достаточно ли это строгая проверка на то является ли сущность экземпляром объекта localStorage?

https://github.com/someApprentice/Crypter/blob/master/src/app/models/StorageWrapper.ts
В правильной ли директории находится эта обёртка? Это не совсем сущность, например как User, но места по лучше я не могу придумать для неё. Куда следует помещать обёртки?

https://github.com/someApprentice/Crypter/blob/master/src/app/auth/registration/registration.component.spec.ts#L67
https://github.com/someApprentice/Crypter/blob/master/src/app/auth/login/login.component.spec.ts#L66
Нужно ли делать проверку на каждую валидацию? Например на встроенные в Ангуляр валидаторы или на валидацию по регулярному выражению? https://github.com/someApprentice/Crypter/blob/master/src/app/auth/registration/registration.component.ts#L21-L24


Следующий шаг написания сервиса сообщений.

Он будет реализован с помощь протокола WAMP и на платформе от https://crossbar.io/ , которая написана на Питоне, и для которой для аутентификации клиентов нужно тоже написать код на нём.

https://github.com/crossbario/crossbar-examples/blob/master/authentication/ticket/dynamic/authenticator.py

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

Заодно и реактивное программирование подучу.


Буду признателен за проверку моего завершающего задания на JavaScript.

Это очень много значит для меня. Большое спасибо.
Ответы: >>1356149 >>1361814 >>1361815
someApprentice 2019/02/27 07:32:12  №1356149 2
>>1354910
>Но получив некий опыт на Ангуляре, я понял что более шаблонным и следовательно простым для понимания было бы обернуть все эти компоненты в отдельный компонент AuthComponent и в нём уже сделать свойство email (а лучше объект user/form) и передавать это значение с помощью "Взаимодействий Компонентов" https://angular.io/guide/component-interaction.

Здесь не получится реализовать "Взаимодействие Компонентов" потому что, всё равно, придется размещать не сами компоненты, а <router-outlet>.
Аноним 2019/03/10 06:00:32  №1361814 3
>>1354910

Вот что мне в ноде кажется неудобным - так это то, что каждый в Node.JS организует код как хочет. Вот, например, код: bodyParser.json() - непонятно, почему он сделан в виде вызова функции? Если он есть в единственном экземпляре, можно просто было бы использовать свойство bodyParser.jsonParser. Если там каждый раз надо создавать новый объект, можно было бы экспортировать класс: new bodyParser.JsonParser. А тут надо вызвать функцию, причем в ее названии нет ни малейшего намека на то, что это функция-конструктор (хоть бы createJsonParser ее назвали). Понятно, что это сторонняя библиотека, но все же не привычно.

А еще я открыл для себя, что в Express функция get() делает 2 разных вещи, в зависимости от числа аргументов: http://expressjs.com/ru/api.html#app . Кажется, разработчики Ноды решили пройтись по тем же граблям, которые проходили разработчики PHP.

Но в общем-то, я довольно плохо знаком с Нодой. Потому перейдем к коду.

Комментариев, конечно мало. Наверно стоило бы хотя бы в начале файла писать, что это за модуль и для чего.

Тут ссылка на отсутствующий в репозитории файл - это нормально? Этот файл ведь из чего-то генерируется, и нельзя ли тут подключать исходный файл. Или хотя бы комментарий написать:

https://github.com/someApprentice/Crypter/blob/master/server.ts#L30
> const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require('./dist/server/main.js');

Это место очень трудно понять. Мне пришлось открыть вопрос по ссылке из комментария и прочесть его:

> // https://stackoverflow.com/a/51391081
> const asyncHandler = fn => (req, res, next) => {

И проблема тут в плохом названии и отсутствии комментариев. Нужно было назвать функцию, например, promiseToCallback, handlePromise или adaptPromise и снабдить комментарием (я еще убрал стрелки так как на мой личный вкус, они плохо читаемы):

// Комментарий
function wrapPromise(asyncFn) {
return function(req, res, next) {
return Promise....;
}
}

Еще я не очень понял:

1) зачем там нужен Promise.resolve(), если асинхронная функция при вызове и так возвращает нам промис.
2) зачем использовать return

Я бы тогда написал так:

// Принимает на вход асинхронный обработчик, и возвращает
// новый обработчик, который отлавливает выброшенные
// в нем асинхронно исключения, передавая их в next().
//
// ссылка на SO
function adaptPromise(asyncFn) {
return function (req, res, next) {
asyncFn(req, res, next).catch(next);
}

}

> Does Bearer token provide XSRF protection?

Я думаю, да, тут XSRF не пройдет, так как браузер автоматически добавляет к отправляемым запросам куки или заголовок Authorization для традиционной авторизации по логину-паролю (на чем и основана уязвимость), но не добавляет его для типа Bearer.

> if (err) throw new err;

А не throw err? err - это функция-констуктор или объект исключения?

> res.cookie('uuid', uuid, ...
> res.cookie('email', email, ...
> res.cookie('name', name, ...

А зачем тут столько кук? Они же уже содержатся внутри токена. А в твоем варианте пользователь может их подменить, и если ты доверяешь им на сервере, то получается уязвимость. Ну и вообще, нужно ли ставить куки в SPA? Это же серверное API, оно наверно не должно ставить куки. А должно отдавать JSON с токеном.

> json(<U> { uuid, email, name, jwt });

Не очень понятно, зачем здесь влеплен Type Assertion? Функции json() он не нужен, это для проверки, что объект соответствует интерфейсу User?

Еще, кстати, вопрос: а при регистрации проверяется уникальность email? Также, я подозреваю, что удобнее может быть сделать отдельную функцию для валидации, чем использовать только встроенные возможности sequelize.

По поводу JWT - я с ним не работал (но с интересом прочитал про него), но у меня уже есть сомнения: почему при подписании токена мы передаем все значения из модели? Получается, что токен перестанет работать в следующих ситуациях:

- пользователь меняет имя
- пользователь меняет email
- пользователь меняет пароль
- мы добавили новые поля в модель пользователя

На мой взгляд, токен должен переставать работать только при смене пароля (на случай, если пользователь боится, что у него украли токен и меняет пароль, старый токен должен перестать действовать). Соответственно, в подписи должны участвовать только uuid и хеш пароля. Или я ошибаюсь?

И, кстати, а почему был выбран JWT? Какие-то еще варианты рассматривались?

Также, я не уверен, а правильно ли токен создавать прямо в обработчике? Не логичнее ли сделать функцию такого вида:

async function createToken(u: User): Promise<string>

Чтобы мы могли бы создавать токены в любом месте API?

> let user = new User({ email });
> await user.validate({ skip: difference(Object.keys(User.rawAttributes), ['email']) });

Это, конечно, выглядит немного корявым способом проверить email. Наверно, можно как-то получить функцию валидации отдельного поля?

> router.get('/email/:email'

Это не очень соответствует REST, так как URL в нем это указатель на "ресурс" (какую-то сущность), и при отсутствии ресурса ты должен отдавать 404. А ты всегда отдаешь 200. Я бы сделал тогда просто /email-exists?email=....

Не очень понятно, зачем ты тут вызвал валидацию и не использовал результат.

И еще важный момент. У тебя нет документации по API, а это плохо. Если фронтенд-разработчик хочет использовать твое API, где он прочтет документацию? Код что ли разбирать?

В наши дни наверно самый популярный формат документирования - это OpenAPI 3.0 (бывший Swagger). Ты описываешь свое API в виде YAML файла, и получаешь на выходе такую красивую штуку: https://petstore.swagger.io/ или такую: https://docs.discourse.org/

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

Еще, кстати, мне интересно, можно ли как-то использовать описания моделей (например, User) для автоматической генерации документации о формате JSON-ответа, и можно ли использовать описания для автоматического тестирования (тест берет Swagger-описание, проходится по всем методам и пробует их вызывать и проверяет, что ответ соответствует описанию).

Ответы: >>1366102 >>1366103
Аноним 2019/03/10 06:03:16  №1361815 4
>>1354910

По тесту:

https://github.com/someApprentice/Crypter/blob/master/api/api.test.ts#L31

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

В твоем случае надо сформулировать требования к функции регистрации. На мой взгляд, они такие:

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

Для каждого требования мы придумываем свой способ проверки. Создание аккаунта на сервере можно проверить, попробовав залогиниться в него. Токен можно проверить, попробовав вызвать защищенное API, например "получить сведения о себе".

В общем, тесты пока сделаны неправильно.

Так как ты тестируешь код, использующий БД, тебе надо предусмотреть, чтобы база была перед тестом в каком-то определенном состоянии (полностью пустая, например, или с какими-то тестовыми данными). Один из вариантов - в самом начале почистить БД/загрузить дамп, а каждый тест обернуть в транзакцию, которая откатывается. Это может быть быстрее, чем загружать дамп каждый раз.

На практике, может быть полезно разместить тестовую БД на ramfs, чтобы избежать вообще обращений к диску и ускорить прогон тестов.

Можно также попробовать добавить тест для обработчика ошибок (вызвать специальный URL, который выбрасывает исключение). Иначе ты можешь его отключить/сломать и не заметить.

> Is there any way to declare all classes at once for a webpack output file?

Я думаю, это не имеет отношения к webpack, так как ты должен в sequelize передать список классов. А для этого их надо импортировать. И вебпак увидит импорты.

По клиентской стороне:

Ты в API при логине ставишь куки вручную, но разве это правильно? По идее, это должно работать так:

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

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

Тут конечно мы упираемся в проблему, что объем кук ограничен, в то время как в браузере мы можем кучу информации сохранить в localStorage. Решением наверно тут будет как можно меньше сохранять в storage, когда мы выполняемся в режиме рендера на сервере. В идеале - хранить там только JWT.

Реализовать разную логику для работы на клиенте и на сервере можно, например, через интерфейс:

interface Storage {
getJwtToken(): Promise<string>
setJwtToken(token)
getUserData(): Promise<User>
setUserData()
}

И 2 реализации: клиентская берет данные из localStorage, а серверная - берет из БД или кук. Но это, конечно, требует писать два варианта кода.

Есть еще альтернатива, но она довольно сложная. Мы могли бы сделать что-то вроде "сессий" и делать для каждой "сессии" свой серверный аналог localStorage. То есть пользователь заходит на сайт, на сервере создается некий serverStorage, который не очищается между запросами. И приложение Angular, работая на сервере, работает с ним, как будто это localStorage. Ключом сессии может быть токен JWT. Данные serverStorage могут храниться в памяти Node-приложения или сбрасываться в HASH внутри редис.

То есть, когда приложение работает на сервере, в итоге данные пишутся в redis с ключом на основе JWT. Там конечно еще можно подумать над оптимальным по производительности алгоримом. И опять же, стараться при работе на сервере меньше сохранять данные в serverStorage, если они есть в БД.

https://github.com/someApprentice/Crypter/blob/master/src/app/app.component.html

Тут мне не кажется 100% надежной проверка залогиненности. Тут же нет проверки, что это валидный токен.

И еще, я не очень понял, как будет работать форма регистрации в режиме серверного рендеринга. Как и куда она будет отправлять свои данные?

> let route = this.router.config.find(r => r.path === redirect);
> route.data['email'] = email;

А это передает email только один раз, или ты этот email навсегда в конфиг роутинга вписал? По моем, не очень красиво получается. Разве нельзя передавать email внутри route parameters: https://angular.io/guide/router#route-parameters

Тогда можно сделать так: router.navigate(['/register', { email: email }])

Причем, я бы хранил email не в path, а в query: /register?email=xyz@example.com

> Позволяет ли Bearer token защититься от XSRF?
> Приходит ли вам на ум какая-нибудь слабая точка которую можно эксплуатировать?

Да. У тебя используются куки, и в них тоже есть токен. Предположим, у нас есть HTML версия, работающая без ангулара. Не получится ли, что злоумышленник сделает форму, которая будет имитировать отправку запроса этой HTML версией, и возникнет XSRF?

> Достаточно ли это строгая проверка на то является ли сущность экземпляром объекта localStorage?

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

> В правильной ли директории находится эта обёртка? Это не совсем сущность, например как User, но места по лучше я не могу придумать для неё. Куда следует помещать обёртки?

Это сервис. Помещать - не знаю, в папку для сервисов? Но у меня есть ощущение, что в клиентском приложении файлы и так довольно хаотично разбросаны.

> Нужно ли делать проверку на каждую валидацию? Например на встроенные в Ангуляр валидаторы или на валидацию по регулярному выражению?

Нет смысла тестировать протестированное. Валидатор если и тестировать, то юнит-тестами, и они уже есть в сторонней библиотеке. Но надо протестировать, что:

- эти валидаторы действительно вызываются и проверяют данные
- их результат работы отображается

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

> Следующий шаг написания сервиса сообщений.

> Он будет реализован с помощь протокола WAMP и на платформе от https://crossbar.io/ , которая написана на Питоне, и для которой для аутентификации клиентов нужно тоже написать код на нём.

А чем Node не годится, кстати? Или ты решил изучить Питон и микросервисы? Ок, но на практике такой маленький проект может быть невыгодно разбивать на микросервисы - у них ведь есть и накладные расходы.

> Поэтому я сейчас буду изучать его, и возможно у меня появиться небольшие вопросы по нему. Могу я задать их в этом треде?

> Заодно и реактивное программирование подучу.

Конечно. Только пиши тогда, что именно ты добавил в проект и на что стоит посмотреть, а то он большой и с ходу это будет не очевидно.

Я пока все не успел проверить, увы, давай сначала с тем, что я написал, разберемся, а потом остальное глянем.

Ответы: >>1366103 >>1366104
someApprentice 2019/03/17 14:55:24  №1366102 5
>>1361814
>Вот что мне в ноде кажется неудобным - так это то, что каждый в Node.JS организует код как хочет. Вот, например, код: bodyParser.json() - непонятно, почему он сделан в виде вызова функции? Если он есть в единственном экземпляре, можно просто было бы использовать свойство bodyParser.jsonParser. Если там каждый раз надо создавать новый объект, можно было бы экспортировать класс: new bodyParser.JsonParser. А тут надо вызвать функцию, причем в ее названии нет ни малейшего намека на то, что это функция-конструктор (хоть бы createJsonParser ее назвали). Понятно, что это сторонняя библиотека, но все же не привычно.
А ещё с TypeScript некоторые возможности теряются https://github.com/someApprentice/Crypter/blob/master/api/api.ts#L8-L9

Моё мнение что на JS не должно быть никакого кода, кроме браузерного, и максимум выдаваться SSR страницы. А серверный код должен написан на языках получше, например PHP.


>Комментариев, конечно мало. Наверно стоило бы хотя бы в начале файла писать, что это за модуль и для чего.
Ой-ой. Ошибка. Нужно было предупредить, что я комментарии оставлял "для себя" чтобы понимать что это и откуда взялось, и что это скорее всего нужно исправить.
К сожалению, инструменты для написания кода на JS ещё не совершены, и не справляются с тривиальными задачами, по моему приземлённому мнению.
Я перенёс эти проблемы в issues задачи https://github.com/someApprentice/Crypter/issues проверять это не нужно, но если у вас есть подсказка, то я с радостью её приму

Исправлю эту ошибку написав хорошие комментарии. Только, я не понимаю зачем, по-моему только сервису-обёртке хранилища можно, потому что это весьма не типичный подход. В остальном, разве это не очевидно что чем занимается?

У меня плохо с английским и написание комментариев утяжелит и замедлил разработку. Насколько важно их писать?


>Тут ссылка на отсутствующий в репозитории файл - это нормально? Этот файл ведь из чего-то генерируется, и нельзя ли тут подключать исходный файл. Или хотя бы комментарий написать:
>
>https://github.com/someApprentice/Crypter/blob/master/server.ts#L30
>> const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require('./dist/server/main.js');
>
Да, эта папка генерируется после сборки приложения npm run build. Думаю тут тоже можно добавить комментарий.


>И проблема тут в плохом названии и отсутствии комментариев. Нужно было назвать функцию, например, promiseToCallback, handlePromise или adaptPromise и снабдить комментарием
Насчет комментария это да, их стоит добавить. Но я не понимаю почему вы пишите, что эта функция для того чтобы обрабатывать промисы, это не так. Эта функция нужна, чтобы обработать асинхронный обработчик запросов, который Эксрпресс не поддерживает (пока что?).

// Это не сработает
express.get('/route', async (...) => {
await promise = ...
});

https://github.com/someApprentice/Crypter/issues/12


>> if (err) throw new err;
>
>А не throw err? err - это функция-констуктор или объект исключения?
Объект исключения. Исправлю.


>> res.cookie('uuid', uuid, ...
>> res.cookie('email', email, ...
>> res.cookie('name', name, ...
>
>А зачем тут столько кук? Они же уже содержатся внутри токена. А в твоем варианте пользователь может их подменить, и если ты доверяешь им на сервере, то получается уязвимость. Ну и вообще, нужно ли ставить куки в SPA? Это же серверное API, оно наверно не должно ставить куки. А должно отдавать JSON с токеном.
Они нужны для отрисовки данных при серверном рендеренге. Как я уже писал, куки нужны только для условий отображений https://github.com/someApprentice/Crypter/blob/master/src/app/main/main.component.html#L3 . Возможно uuid можно убрать, но почему нет?

Здесь нет уязвимости, т.к. сервер (API) не использует куки.

Сделать приложение работающие без поддержки JS - я отказываюсь от этой идеи.


>> json(<U> { uuid, email, name, jwt });
>
>Не очень понятно, зачем здесь влеплен Type Assertion? Функции json() он не нужен, это для проверки, что объект соответствует интерфейсу User?
Да, именно так. Это лишнее?

>Еще, кстати, вопрос: а при регистрации проверяется уникальность email?
Нет, я забыл её сделать. Исправлю. И наверно нужно ещё это в схему БД добавить.

>Также, я подозреваю, что удобнее может быть сделать отдельную функцию для валидации, чем использовать только встроенные возможности sequelize.
Почему нужно отдельную функцию валидации? Всё же и так в одну строчку делается. https://github.com/someApprentice/Crypter/blob/master/api/api.ts#L50



>По поводу JWT - я с ним не работал (но с интересом прочитал про него), но у меня уже есть сомнения: почему при подписании токена мы передаем все значения из модели? Получается, что токен перестанет работать в следующих ситуациях:
>
>- пользователь меняет имя
>- пользователь меняет email
>- пользователь меняет пароль
>- мы добавили новые поля в модель пользователя
>
>На мой взгляд, токен должен переставать работать только при смене пароля (на случай, если пользователь боится, что у него украли токен и меняет пароль, старый токен должен перестать действовать). Соответственно, в подписи должны участвовать только uuid и хеш пароля. Или я ошибаюсь?
>
>И, кстати, а почему был выбран JWT? Какие-то еще варианты рассматривались?
>
>Также, я не уверен, а правильно ли токен создавать прямо в обработчике? Не логичнее ли сделать функцию такого вида:
>
>async function createToken(u: User): Promise<string>
>
>Чтобы мы могли бы создавать токены в любом месте API?

>почему при подписании токена мы передаем все значения из модели?
Да, я понимаю этот вопрос. Я думал, что можно сделать так, получать из Bearer token этот токен, и получать из него всё что нужно (имейл/что-нибудь ещё), но это, скорее всего нигде не пригодиться, так что это ошибка. Я согласен что достаточно хранить в нём только uuid и хэш.

>И, кстати, а почему был выбран JWT? Какие-то еще варианты рассматривались?
Он популярен. Чем популярнее технология, тем больше решений вопросов связанной с ней. В прочитанных мной статьях по JS приводят примеры именно с ним.


>Также, я не уверен, а правильно ли токен создавать прямо в обработчике? Не логичнее ли сделать функцию такого вида:
>
>async function createToken(u: User): Promise<string>
>
>Чтобы мы могли бы создавать токены в любом месте API?
Я скажу на чистоту мой взгляд на это - Я не понимаю зачем нужно выносить такие функции в отдельные. Это функция понятна любому разработчику и он с первого взгляда поймет что происходит в ней. Почему нужно делать обёртку ради человекопонятного названия функции?

Я правда не понимаю, вот как эта функция будет выглядеть (она кстати не работает на промисах):

function createToken(u: User, cb) {
token.sign(u.dataValues, JWT_SECRET, cb(...))
}

Но мы и так передаём в неё пользователя и коллбэк, и больше ничего не делаем. Почему нужно её выносить?


>> let user = new User({ email });
>> await user.validate({ skip: difference(Object.keys(User.rawAttributes), ['email']) });
>
>Это, конечно, выглядит немного корявым способом проверить email. Наверно, можно как-то получить функцию валидации отдельного поля?
Это баг библиотеки sequelize-typescript. Чтобы получить валидацию отдельного поля нужно вместо опции skip добавить fields.


>> router.get('/email/:email'
>
>Это не очень соответствует REST, так как URL в нем это указатель на "ресурс" (какую-то сущность), и при отсутствии ресурса ты должен отдавать 404. А ты всегда отдаешь 200. Я бы сделал тогда просто /email-exists?email=....
>Я бы сделал тогда просто /email-exists?email=....
Всё исправлю. Здесь тоже в случае отсутствия нужно отдавать 404?

>Не очень понятно, зачем ты тут вызвал валидацию и не использовал результат.
Хм, возможно валидацию и не стоило вызывать. Почему нет?

>и не использовал результат
Результат валидации либо вбрасывает ошибку, либо возвращает null(?). Эта ошибка обрабатывается в обработчике ошибок https://github.com/someApprentice/Crypter/blob/master/api/errorHandler.ts#L8-L10



>И еще важный момент. У тебя нет документации по API, а это плохо. Если фронтенд-разработчик хочет использовать твое API, где он прочтет документацию? Код что ли разбирать?
>
>В наши дни наверно самый популярный формат документирования - это OpenAPI 3.0 (бывший Swagger). Ты описываешь свое API в виде YAML файла, и получаешь на выходе такую красивую штуку: https://petstore.swagger.io/ или такую: https://docs.discourse.org/
>
>Естественно, логично не писать YAML руками, а генерировать его из комментариев в коде. Так ты получишь и комментированный код, и документацию для разработчиков.
Понял, буду работать над этим. Написать комментарии для документации API мне кажется более понятной задачей, чем писать комментарии для бушующих разроботчиков.

Так вы имели ввиду комментраии для модулей API, а не фронтенд приложения?
Ответы: >>1366731 >>1366734
someApprentice 2019/03/17 14:55:51  №1366103 6
>>1361814
>Еще, кстати, мне интересно, можно ли как-то использовать описания моделей (например, User) для автоматической генерации документации о формате JSON-ответа, и можно ли использовать описания для автоматического тестирования (тест берет Swagger-описание, проходится по всем методам и пробует их вызывать и проверяет, что ответ соответствует описанию).
Я ничего из этого не знаю, но постараюсь разобраться.


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

>Если мы тестируем функцию вычисления корня, то ее проверяют возведением в квадрат, а не аналогичным вычислением.
Не понимаю сравнение.

var n = 5
expect(sqrt(n^2)).toEqual(n)

Я же тоже "возвожу" данные пользователя в токен и проверяю совпадает ли возвращённый ответ, а именно кукисы, с ним.

var user = { ... }
token(user, (jwt) => {
expect(cookies['jwt']).toEqual(jwt)
});

Ну да, теперь вижу причину. Нужно проверить что пришедший токен в дешифрованном виде равен данным пользователя?

>В твоем случае надо сформулировать требования к функции регистрации. На мой взгляд, они такие:
>
>- она должна создавать аккаунт на сервере, под которым можно залогиниться
Я забыл сделать проверку, что пользователь добавился в БД и данные правильные. Это достаточное условие? Залогинивание проверяется в предназначенном для неё тесте, и там как раз сначала заполняется запись в БД.

>- она должна возвращать токен, с которым можно использовать защищенное API
Нужно сделать проверку как я писал выше, что пришедший токен в дешифрованном виде равен данным пользователя или действительно нужно проверять каким-то методом API?

>- нельзя дважды зарегистрироваться с одинаковым email
Сделаю. то нужно сделать прописав ассоциацию Sequelize на уникальность emai'а. Тогда при повтороном имейле будет вбрасываться ошибка и тест провалится.

>- нельзя регистрироваться без указания обязательных аргументов
Сделаю.

>- и, может, еще какие-то требования по валидации. Например, что при ошибке возвращается объект определенного вида
Сделаю.


>Для каждого требования мы придумываем свой способ проверки. Создание аккаунта на сервере можно проверить, попробовав залогиниться в него. Токен можно проверить, попробовав вызвать защищенное API, например "получить сведения о себе".
>Создание аккаунта на сервере можно проверить, попробовав залогиниться в него.
>Токен можно проверить, попробовав вызвать защищенное API, например "получить сведения о себе".
Почему? Разве не достаточно проверить что запись добавилась в базу? Разве не залогинивание не должно проверяться в своём собственном тесте?
А токен не достаточно проверить просто дешифровав его (как раз метод и называется verify(token))?


>Так как ты тестируешь код, использующий БД, тебе надо предусмотреть, чтобы база была перед тестом в каком-то определенном состоянии (полностью пустая, например, или с какими-то тестовыми данными). Один из вариантов - в самом начале почистить БД/загрузить дамп, а каждый тест обернуть в транзакцию, которая откатывается. Это может быть быстрее, чем загружать дамп каждый раз.
В данный момент для каждого теста БД должна быть в чистом состоянии и после каждого теста база очищается это можно улучшить добавив перед каждым тестом очистку (методо beforeEach()).

>На практике, может быть полезно разместить тестовую БД на ramfs, чтобы избежать вообще обращений к диску и ускорить прогон тестов.
Тяжело понять. Я с таким не сталкивался. Вижу что это как бы "эмулирует" жесткий диск в оперативной памяти. Как для этого настроить psql? Как нужно будет для это настраивать найстройки соеденения к БД в Ноде? Слишком много вопросов для такой маленькой задачи. Возможно позже, с ростом приложения это будет актуально. Если что взял этот подход на вооружение и вернусь к нему, когда тесты будут делаться медленно.


>Можно также попробовать добавить тест для обработчика ошибок (вызвать специальный URL, который выбрасывает исключение). Иначе ты можешь его отключить/сломать и не заметить.
Сделаю.

>> Is there any way to declare all classes at once for a webpack output file?
>
>Я думаю, это не имеет отношения к webpack, так как ты должен в sequelize передать список классов. А для этого их надо импортировать. И вебпак увидит импорты.
В sequlize-typescript можно указать директорию с моделями, но webpack транспилирует всё в один файл, и это ломается.

https://www.npmjs.com/package/sequelize-typescript#configuration
https://github.com/someApprentice/Crypter/issues/7


>По клиентской стороне:
>
>Ты в API при логине ставишь куки вручную, но разве это правильно? По идее, это должно работать так:
Ставлю куки вручную в клиентской стороне? Нет, у меня такого нет. В клиентской стороне куки только берутся из реквеста, чтобы пререндерить страницу с залогиненым пользователем или нет. Для этого они и нужны. localStorage ставиться в ручную(?) в клиентской стороне при залогинивании https://github.com/someApprentice/Crypter/blob/master/src/app/auth/login/login.component.ts#L48-L51 . Не могли бы вы уточнить строку где я это делаю, я не могу понять последующие описание проблемы.


>https://github.com/someApprentice/Crypter/blob/master/src/app/app.component.html
>
>Тут мне не кажется 100% надежной проверка залогиненности. Тут же нет проверки, что это валидный токен.
Так это же отображение. Если что-то не так, то при запросе к API выдастся соответствующая ошибка. Вряд ли, пользователь сможет что-то сломать не лазия в хранилище.


>И еще, я не очень понял, как будет работать форма регистрации в режиме серверного рендеринга. Как и куда она будет отправлять свои данные?
>
>> let route = this.router.config.find(r => r.path === redirect);
>> route.data['email'] = email;
Имеете ввиду версию страницы работающей с отключенным JS? Я решил отказаться от этой идеи. Она бесполезная, по крайней мере для части чата, потому что сообщения будут шифроваться на стороне клиента. Может быть позже можно что-то с этим придумать, но пока этого не будет. Я считаю, что это вопрос доверия пользователя, оно есть в каких-то границах, и эти границы могут как и широки так и малы, т.е. я пытаюсь сказать что зависит от пользователя и его доверия. Я думаю, что для приложения с открытом кодом можно даже задуматься и о вайтлисте js.

>А это передает email только один раз, или ты этот email навсегда в конфиг роутинга вписал? По моем, не очень красиво получается. Разве нельзя передавать email внутри route parameters: https://angular.io/guide/router#route-parameters
>
>Тогда можно сделать так: router.navigate(['/register', { email: email }])
>
>Причем, я бы хранил email не в path, а в query: /register?email=xyz@example.com
Можно, но мне наоборот кажется это не очень красивым, когда в URL что-то появляется. Хочу чтобы было так.
Этот email удаляется из роутинга после уничтожения связанного компонента:

https://github.com/someApprentice/Crypter/blob/master/src/app/auth/login/login.component.ts#L63-L68


>> Позволяет ли Bearer token защититься от XSRF?
>> Приходит ли вам на ум какая-нибудь слабая точка которую можно эксплуатировать?
>
>Да. У тебя используются куки, и в них тоже есть токен. Предположим, у нас есть HTML версия, работающая без ангулара. Не получится ли, что злоумышленник сделает форму, которая будет имитировать отправку запроса этой HTML версией, и возникнет XSRF?
Как я говорил ранее, я принял решение отказаться от версии без ангуляра, что значит что не будет такого обработчика запроса, который будет работать с куками повторюсь в очередной раз, что они нужны только для условий в отоброжении (токен наверно и удалить из кук)


>> Достаточно ли это строгая проверка на то является ли сущность экземпляром объекта localStorage?
>
>Проверка достаточная, но непонятен ее смысл. Тест по идее должен проверять, что данные сохраняются и загружаются, а не какой именно класс там испольуется для хранения.
Проверить... что, например, вот для серверной части есть проверка, что вернулся StorageWrapper и всё работает, а для браузерной тоже аналогично. Это не нужно?
someApprentice 2019/03/17 14:56:40  №1366104 7
>>1361815
>> Следующий шаг написания сервиса сообщений.
>
>> Он будет реализован с помощь протокола WAMP и на платформе от https://crossbar.io/ , которая написана на Питоне, и для которой для аутентификации клиентов нужно тоже написать код на нём.
>
>А чем Node не годится, кстати? Или ты решил изучить Питон и микросервисы? Ок, но на практике такой маленький проект может быть невыгодно разбивать на микросервисы - у них ведь есть и накладные расходы.
У роутеров WAMP'а на Node, нету возможностей для аутентифекации и авторизации соединения, и документации. Вышеупомянутая платформа развита лучше других.


>> Поэтому я сейчас буду изучать его, и возможно у меня появиться небольшие вопросы по нему. Могу я задать их в этом треде?
>
>> Заодно и реактивное программирование подучу.
>
>Конечно. Только пиши тогда, что именно ты добавил в проект и на что стоит посмотреть, а то он большой и с ходу это будет не очевидно.
Хорошо, я буду всё подробно описывать.


>Я пока все не успел проверить, увы, давай сначала с тем, что я написал, разберемся, а потом остальное глянем.
Хорошо, спасибо большое! Я не успеваю ответить в тот же день. Не понимаю почему - занимаюсь каждый день, и всё больше и больше нужно изучить. Я так хочу просто писать код. Я никогда не знал что разработка на JS может быть такой сложной, и приложений вообще.
Я ещё серьёзно задумываюсь о приложениях для устройств, а там либо медленный и кривой JS, либо изучать Java/Swift.

Я так хочу хотя бы браузернное приложение к лету закончить, а приложения к осени.


Спасибо большое, у меня нет слов как мне всё это важно.
Аноним 2019/03/18 17:34:08  №1366731 8
>>1366102

А напомни пожалуйста, зачем ты решил делать server-side rendering, если ты все равно поддерживаешь только браузеры с JS? Для изучения SSR?

Просто обычно SSR используется либо для поддержки поисковых ботов, либо для пользователей без JS или для создания "облегченной" версии. У тебя же ситуация усложняется тем, что ты пытаешься на сервере имитировать localStorage. Если бы у тебя был сайт вроде инстаграма или твиттера, то там можно было бы сделать SSR для поисковиков, и не было бы никаких сложностей. А для страниц с авторизацией делать SSR чуть сложнее.

То есть, я хочу понять, зачем и как используется SSR и что в этом случае можно сделать с хранилищем для сервера. Идея передавать кучу данных в куках мне не особо нравится.

> Исправлю эту ошибку написав хорошие комментарии. Только, я не понимаю зачем, по-моему только сервису-обёртке хранилища можно, потому что это весьма не типичный подход. В остальном, разве это не очевидно что чем занимается?

Да, комментарии нужны там, где назначение модуля не очевидно.

> https://github.com/someApprentice/Crypter/issues/1

Тут я кстати обратил внимание, что у тебя в стектрейсе обфусцированные имена функций. Лучше было бы в dev использовать неминифицированные файлы, с исходными названиями функций, а то сложно разбираться в функции с именем t._e(). Не знаю, где это настраивается, может быть, в angular.json (но я не уверен).

Вообще, по моему мнению, в dev удобнее не склеивать файлы, а подгружать динамически (через что-нибудь вроде require.js или загрузчик для модулей ES6). Тогда в dev tools и в стек трейсах ты видишь файлы с теми же именами, как в оригинале, а не огромный main.js. Но я, увы, не знаю, легко ли это сделать в твоем случае.

> У меня плохо с английским и написание комментариев утяжелит и замедлил разработку. Насколько важно их писать?

Там, где я работаю, проблему решили написанием комментариев на русском языке. А так, я думаю, что важно. Пока ты работаешь один, ты многое можешь держать в голове. Но что, если придет новый человек, и ему надо разобраться с кодом? Тут важно не количество комментариев, а насколько легко разобраться в проекте.

У тебя в README даже не описана общая архитектура, как все связано. Я не очень хорошо знаком со всеми этими компиляторами и средствами сборки, и я путаюсь от того, что там в корне лежит куча разных конфигов. Если бы в README было описано, как происходит процесс сборки и какой конфиг за что отвечает, думаю, было бы понятнее.

> Эта функция нужна, чтобы обработать асинхронный обработчик запросов, который Эксрпресс не поддерживает (пока что?).

Я имею в виду, что когда смотришь на код, абсолютно непонятно, зачем эта функция и что за магию она делает. Я не знал, поддерживает ли express возврат промисов из обработчиков (теперь знаю, что нет). А если бы там был комментарий - было бы понятнее. Ну и мне кажется, функцию, которая адаптирует асинх. функцию к использованию в express вполне можно называть адаптером.

> Как я уже писал, куки нужны только для условий отображений

По идее, эти данные на сервере проще получить из БД по токену авторизации. Значит, куки можно было бы не делать. То есть тут получается избыточность, как мне кажется.

>> Функции json() он не нужен, это для проверки, что объект соответствует интерфейсу User?
> Да, именно так. Это лишнее?

Нет, я просто хотел спросить, зачем так сделано.

> Почему нужно отдельную функцию валидации? Всё же и так в одну строчку делается. https://github.com/someApprentice/Crypter/blob/master/api/api.ts#L50

Пока это не требуется, но если ты захочешь сделать проверку уникальности или другую проверку, требующую обращения к БД или сервисам, то из модели ты не сможешь это сделать, так как у нее нет ссылок на эти сервисы. И тебе понадобится внешняя функция валидации.

> Я скажу на чистоту мой взгляд на это - Я не понимаю зачем нужно выносить такие функции в отдельные. Это функция понятна любому разработчику и он с первого взгляда поймет что происходит в ней. Почему нужно делать обёртку ради человекопонятного названия функции?

Чтобы избежать дублирования кода. Чтобы не надо было в нескольких местах править одинаковый код. Ну и чтобы не писать код длинной портянкой, а разделять на отдельные части.

> Я правда не понимаю, вот как эта функция будет выглядеть

function createToken(u: User): Promise<string> - наверно так?

> Всё исправлю. Здесь тоже в случае отсутствия нужно отдавать 404?

По идее, да, можно так, если ты хочешь следовать идеям из REST. Либо можно возвращать JSON с true/false, если не хочешь.

В идеологии REST (да и в стандартах HTTP что-то может быть), как я помню, URL соответствуют ресурсам, по типу:

/user/123 - пользователь 123
/book/some-book - какая-то книга

И ты можешь слать к этим ресурсам запросы GET/POST/PUT/DELETE. Такая схема очень хорошо работает с кешем - ты можешь просто использовать URL ресурса в качестве ключа кеша. У REST могут быть и минусы, например, может потребоваться делать много запросов для получения всех данных (хотя это зависит от того, как спроектировано API).

Потому не все используют REST. Например, у Ютуба в API для получения списка видео и одного видео используется один и тот же эндпойнт, просто с разными параметрами: https://developers.google.com/youtube/v3/docs/videos/list

> Результат валидации либо вбрасывает ошибку, либо возвращает null(?). Эта ошибка обрабатывается в обработчике ошибок

Там теряются подробности ошибки:

if (err instanceof ValidationError) {
return res.sendStatus(400);
}

Ну и довольно неочевидная штука, я бы не додумался лезть в обработчик ошибок смотреть код.
Ответы: >>1367597
Аноним 2019/03/18 17:36:18  №1366734 9
>>1366102

> Так вы имели ввиду комментраии для модулей API, а не фронтенд приложения?

Главное, чтобы в коде было проще разбираться.

> Я же тоже "возвожу" данные пользователя в токен и проверяю совпадает ли возвращённый ответ, а именно кукисы, с ним.

Ты в тесте просто скопировал код из серверного API. Если ты его неправильно написал, то ты мог сделать одинаковую ошибку и в серверном коде, и в тесте. Потому проверять работу функции нужно другим кодом, другим алгоритмом, а не тем же самым.

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

Да и это нелогично, получатель токена ведь не должен знать, как он устроен внутри. Ему достаточно того, что этот токен дает доступк API, а как он устроен - не его дело.

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

Тесты проверяют требования (ТЗ) для API. У тебя в требованиях (если их сформулировать и записать пиьменно) вряд ли сказано, что "функция регистрации возвращает токен JWT такой-то структуры, зашифрованный таким-то ключом". Она возвращает просто токен, неважно какой. Знание о том, как он устроен, должно быть сконцентрировано в модуле для генерации и проверки токена, а остальным знать это не важно. Потому я и советовал вынести функции генерации и проверки токена в отдельный модуль авторизации.

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

> Ну да, теперь вижу причину. Нужно проверить что пришедший токен в дешифрованном виде равен данным пользователя?

Мне кажется, это будет плохая идея. Получатель токена не должен знать, как он устроен.

> Я забыл сделать проверку, что пользователь добавился в БД и данные правильные. Это достаточное условие? Залогинивание проверяется в предназначенном для неё тесте, и там как раз сначала заполняется запись в БД.

Я думаю, что удобнее тестировать API снаружи, не имея доступа к БД, а используя только само API. Это позволяет тесту не знать о том, как и где хранятся данные, и нам не придется переделывать тест при каких-то изменениях в БД. Это экономит наше время.

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

> Тяжело понять. Я с таким не сталкивался. Вижу что это как бы "эмулирует" жесткий диск в оперативной памяти. Как для этого настроить psql?

Я почитал тут:

- https://ru.wikipedia.org/wiki/Tmpfs
- http://www.k-max.name/linux/ramdisk-ramfs-tmpfs-in-linux/

Лучше использовать tmpfs. По идее, ты создаешь диск и монтируешь его в папку одной командой (могут понадобиться права рута, но можно прописать этот диск в /etc/fstab с опцией монтирования пользователем). tmpfs не тратит память, если на виртуальном диске нет данных, более того, она по моему не создает структуру каталогов и служебные области, а как-то хранит файлы напрямую в памяти.

После этого ты как-то настраиваешь psql, чтобы данные для тестовой базы хранились бы в папке с tmpfs. Я не знаю, как это сделать, но гугление выдает статьи вроде такой https://www.manniwood.com/postgresql_94_in_ram которая описывает создание отдельного инстанса Postgres с данными в tmpfs.

> В sequlize-typescript можно указать директорию с моделями, но webpack транспилирует всё в один файл, и это ломается.

Можно придумать какой-то костыль, например, читать список файлов и создавать файл "models.js", импортирующий все эти модели. Пока моделей мало, проще просто вручную перечислить.

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

Проблему можно решить, выложив клиентский код в открытый доступ и собрав из него electron-приложение. Если мы сами собрали его из проверенных исходников, то никто не сможет подменить код клиента.

> Можно, но мне наоборот кажется это не очень красивым, когда в URL что-то появляется. Хочу чтобы было так.

Так это не сохраняет состояние. Если мы обновим страницу, или используем навигацию в браузере (вперед/назад), то email потеряется. Потому аргументы и хранят в URL.

Тут тогда логичнее было просто сделать единую страницу регистрации/логина на одном роуте с двумя формами и скрывать одну из них. А email хранить в обычной переменной. И не понадобятся такие явно ненадежные костыли.

> Проверить... что, например, вот для серверной части есть проверка, что вернулся StorageWrapper и всё работает, а для браузерной тоже аналогично. Это не нужно?

Проверять это логичнее, пытаясь что-то туда сохранить, а не проверкой, к какому классу относится объект.

> У роутеров WAMP'а на Node, нету возможностей для аутентифекации и авторизации соединения, и документации. Вышеупомянутая платформа развита лучше других.

Да, но это можно добавить. А в случае с Питоном, ты не сможешь вызывать функции из приложения Node, тебе придется возможно дублировать их, или настраивать взаимодействие. Я не против такого варианта, но готовься к сложностям. И надо правильно спроектировать взаимодействие между питоновским и нодовским приложениями. Это уже почти микросервисы получаются со всеми их плюсами и минусами.
Ответы: >>1367598
someApprentice 2019/03/20 14:41:30  №1367597 10
download.png (113, 1920x1051)
1051x1920
>>1366731
>А напомни пожалуйста, зачем ты решил делать server-side rendering, если ты все равно поддерживаешь только браузеры с JS? Для изучения SSR?
>
>Просто обычно SSR используется либо для поддержки поисковых ботов, либо для пользователей без JS или для создания "облегченной" версии. У тебя же ситуация усложняется тем, что ты пытаешься на сервере имитировать localStorage. Если бы у тебя был сайт вроде инстаграма или твиттера, то там можно было бы сделать SSR для поисковиков, и не было бы никаких сложностей. А для страниц с авторизацией делать SSR чуть сложнее.
>
>То есть, я хочу понять, зачем и как используется SSR и что в этом случае можно сделать с хранилищем для сервера. Идея передавать кучу данных в куках мне не особо нравится.

>А напомни пожалуйста, зачем ты решил делать server-side rendering, если ты все равно поддерживаешь только браузеры с JS? Для изучения SSR?
Для ускорения загрузки приложения. И для поддержки поисковых ботов, конечно.

>То есть, я хочу понять, зачем и как используется SSR и что в этом случае можно сделать с хранилищем для сервера. Идея передавать кучу данных в куках мне не особо нравится.
Почему не нравится? Это же тоже самое что запрос к серверной части приложения и его шаблонизатору, для которого нужны куки для отображения, той или иной информации относительно отдельного пользователя.


>> https://github.com/someApprentice/Crypter/issues/1
>
>Тут я кстати обратил внимание, что у тебя в стектрейсе обфусцированные имена функций. Лучше было бы в dev использовать неминифицированные файлы, с исходными названиями функций, а то сложно разбираться в функции с именем t._e(). Не знаю, где это настраивается, может быть, в angular.json (но я не уверен).
>
>Вообще, по моему мнению, в dev удобнее не склеивать файлы, а подгружать динамически (через что-нибудь вроде require.js или загрузчик для модулей ES6). Тогда в dev tools и в стек трейсах ты видишь файлы с теми же именами, как в оригинале, а не огромный main.js. Но я, увы, не знаю, легко ли это сделать в твоем случае.
У меня сейчас получилось сгенерировать не минифицированные файлы, убрав просто флаг --prod из команды сборки ng build --prod && ng run Crypter:server && webpack --config webpack.server.config.js

Добавил новый стактрейс: https://github.com/someApprentice/Crypter/issues/1#issuecomment-474728547

>Тогда в dev tools и в стек трейсах ты видишь файлы с теми же именами, как в оригинале, а не огромный main.js.
Разве и так в dev tools нельзя посмотреть все файлы даже после компиляции webpack'ом?pic-1


>> У меня плохо с английским и написание комментариев утяжелит и замедлил разработку. Насколько важно их писать?
>
>Там, где я работаю, проблему решили написанием комментариев на русском языке. А так, я думаю, что важно. Пока ты работаешь один, ты многое можешь держать в голове. Но что, если придет новый человек, и ему надо разобраться с кодом? Тут важно не количество комментариев, а насколько легко разобраться в проекте.
>
>У тебя в README даже не описана общая архитектура, как все связано. Я не очень хорошо знаком со всеми этими компиляторами и средствами сборки, и я путаюсь от того, что там в корне лежит куча разных конфигов. Если бы в README было описано, как происходит процесс сборки и какой конфиг за что отвечает, думаю, было бы понятнее.
Если честно, я сам путаюсь от такого количества конфигов, и я сам первый раз сталкиваюсь с этими всеми компиляторами и средствами сборки. Это бесконечная яма если я буду и это ещё изучать во всех тонкостях. Мне нужно изучить Python, мне нужно изучить crossbar.io (сегодня собираюсь писать действительный код), мне нужно изучить реактивное программирование, мне нужно написать само приложение, и на каждом шажке всплывает ещё 1000 подводных камней, которые нужно изучать. Я так никогда не закончу эту приложение.

Простите, что противлюсь вашим наставлениям - я исправлю это после всего... А пока, всё что нужно знать о сборке, тому кто хочет протестировать приложение, нужно всего лишь набрать npm run build.

Комментарии в коде для обёртки я обязательно добавлю.


>> Эта функция нужна, чтобы обработать асинхронный обработчик запросов, который Эксрпресс не поддерживает (пока что?).
>
>Я имею в виду, что когда смотришь на код, абсолютно непонятно, зачем эта функция и что за магию она делает. Я не знал, поддерживает ли express возврат промисов из обработчиков (теперь знаю, что нет). А если бы там был комментарий - было бы понятнее. Ну и мне кажется, функцию, которая адаптирует асинх. функцию к использованию в express вполне можно называть адаптером.
Хорошо, я переменную функцию и добавлю к ней более вразумительный комментарий.


>> Как я уже писал, куки нужны только для условий отображений
>
>По идее, эти данные на сервере проще получить из БД по токену авторизации. Значит, куки можно было бы не делать. То есть тут получается избыточность, как мне кажется.
Да, можно написать и серверный код в Angular-приложении за счет проверки на isPlatformServer(), но для этого нужно будет... хм, внутри этой проверки делать отдельные импорты библиотек (sequelize, dotenv, весь серверный код) чтобы они не тянулись в браузерной части. Мне не нравится этот стиль написания кода, так ещё не понятней будет, и к тому же нужно будет писать другую абстракцию - ничего от этого не выигрывается и только теряется время на написание дополнительных комментариев, увеличения кода, роста количества файлов, и что значит сложности в изучении проекта.

Я вижу вашу мысль об избыточности и понимаю, что это связанно с тем что есть и localStorage и кукисы, но почему бы и нет? Это нормально использовать и кукисы и localStorage. Что мешает этому? Это же машинное хранилище, и им нужно пользоваться.
Можно посмотреть на это так - сервер рендерит страницу с помощью кук, а js приложение подхватывает полученную страницу и уже работает с localStorage. То с чем и должен работать js, не получая доступ к кукам. Разве это не плохо что js имеет доступ к кукам?


>> Почему нужно отдельную функцию валидации? Всё же и так в одну строчку делается. https://github.com/someApprentice/Crypter/blob/master/api/api.ts#L50
>
>Пока это не требуется, но если ты захочешь сделать проверку уникальности или другую проверку, требующую обращения к БД или сервисам, то из модели ты не сможешь это сделать, так как у нее нет ссылок на эти сервисы. И тебе понадобится внешняя функция валидации.
А, хорошо. Конечно, для таких случаев я бы написал сервис валидации.

>Чтобы избежать дублирования кода. Чтобы не надо было в нескольких местах править одинаковый код. Ну и чтобы не писать код длинной портянкой, а разделять на отдельные части.
В данный момент тогда лучше сделать сервис с методом .login(...), потому что, пока что, код дублируется в двух местах и нужен как раз для залогинивания. Думаю, всё таки нужно написать сервис Authorizer.


>> Я правда не понимаю, вот как эта функция будет выглядеть
>
>function createToken(u: User): Promise<string> - наверно так?
Я выше написал, что для этой функции нужно передать коллбэк и в итоге получится тоже самое, либо нужно делать отдельную функцию для каждого случая createTokenForLogin(u: User) Promise<string>, createTokenForSomething(smth: any) Promise<string>... Может правда лучше разбить сам код на функции, и там пользоваться функцией создания токена как есть?


>> Результат валидации либо вбрасывает ошибку, либо возвращает null(?). Эта ошибка обрабатывается в обработчике ошибок
>
>Там теряются подробности ошибки:
>
>if (err instanceof ValidationError) {
>return res.sendStatus(400);
>}
>
>Ну и довольно неочевидная штука, я бы не додумался лезть в обработчик ошибок смотреть код.
А как иначе нужно делать? Делать блоки try/catch каждый раз и каждый раз повторять один и тот же код? Разве в PHP на Slim'е не была такая логика обработки ошибки?
someApprentice 2019/03/20 14:41:54  №1367598 11
>>1366734
>> Я же тоже "возвожу" данные пользователя в токен и проверяю совпадает ли возвращённый ответ, а именно кукисы, с ним.
>
>Ты в тесте просто скопировал код из серверного API. Если ты его неправильно написал, то ты мог сделать одинаковую ошибку и в серверном коде, и в тесте. Потому проверять работу функции нужно другим кодом, другим алгоритмом, а не тем же самым.
>
>Плюс, второй недостаток теста - ты в нем используешь знание о том, как устроена функция регистрации, как она генерирует токен. Если в алгоритме генерации токена что-то поменяется, тебе придется преределывать тест.
>
>Да и это нелогично, получатель токена ведь не должен знать, как он устроен внутри. Ему достаточно того, что этот токен дает доступк API, а как он устроен - не его дело.
>
>В твоем случае, если ты вызываешь API регистрации, логичнее всего проверить, что полученный токен можно использовать для доступа к защищенному API. Или хотя бы вызвать функцию проверки токена.
>
>Тесты проверяют требования (ТЗ) для API. У тебя в требованиях (если их сформулировать и записать пиьменно) вряд ли сказано, что "функция регистрации возвращает токен JWT такой-то структуры, зашифрованный таким-то ключом". Она возвращает просто токен, неважно какой. Знание о том, как он устроен, должно быть сконцентрировано в модуле для генерации и проверки токена, а остальным знать это не важно. Потому я и советовал вынести функции генерации и проверки токена в отдельный модуль авторизации.
>
>Принцип разделения ответственности здесь нарушается. Это легко видеть по тому, что код теста использует секрет, который нужен для генерации токена. Тесты ведь имитируют поведение пользователя API. А у него нет этого секрета.

>Плюс, второй недостаток теста - ты в нем используешь знание о том, как устроена функция регистрации, как она генерирует токен. Если в алгоритме генерации токена что-то поменяется, тебе придется преределывать тест.
У меня в тесте с логином, я сначала добавляю пользователя в БД, а потом проверяю само залогиневание. То есть я здесь тоже использую знание о том как устроенна функция. А что если алгоритм сохранения пользователя тоже поменяется? Здесь тоже ошибка? Как тогда быть? Нужно писать какую-то абстракцию над БД во время тестов? Значит, нужно сначала вызвать функцию регистрации?

>Принцип разделения ответственности здесь нарушается. Это легко видеть по тому, что код теста использует секрет, который нужен для генерации токена. Тесты ведь имитируют поведение пользователя API. А у него нет этого секрета.
Мне тяжело понять лежащий глубокий смысл в таких принципах как SOLID. Я похоже нарушил один из его принципов. Напишу здесь чтобы не забыть - что мне нужно детальнее ознакомиться с примерами этих принципов чего я не делал.

В целом, я понял свою ошибку, что я тестировал код не то как это сделал бы человек, а проверки на то что вернёт машина. Я понял, что нужно писать код теста так, чтобы с сымитировать тестирование функционала живым человек на действительном сайте.

Всё исправлю.

>Но на практике, иногда для удобства добавляют специальный тестовые методы в API. Например, ты мог бы сделать тестовый метод проверки наличия пользователя, или проверки токена. Но если можно обойтись без этого, то проще не использовать их, так как это лишний код.
Можно проверить можно ли залогиниться/разлогиниться после этих методов.


>> В sequlize-typescript можно указать директорию с моделями, но webpack транспилирует всё в один файл, и это ломается.
>
>Можно придумать какой-то костыль, например, читать список файлов и создавать файл "models.js", импортирующий все эти модели. Пока моделей мало, проще просто вручную перечислить.
Я плохо разбираюсь как настраивать webpack. В проблемах этой библиотеке, мне сказали, что нужно написать плагин для этого.
Буду пока вручную перечислять, хоть и знаю что это плохо.


>> Я считаю, что это вопрос доверия пользователя, оно есть в каких-то границах, и эти границы могут как и широки так и малы, т.е. я пытаюсь сказать что зависит от пользователя и его доверия. Я думаю, что для приложения с открытом кодом можно даже задуматься и о вайтлисте js.
>
>Проблему можно решить, выложив клиентский код в открытый доступ и собрав из него electron-приложение. Если мы сами собрали его из проверенных исходников, то никто не сможет подменить код клиента.
Вот именно. Код и так будет в открытом доступе. Только, к сожалению, не каждый пользователь сможет сделать это самостоятельно. Даже я думаю, что это не так просто, как вы говорите.

Что вы знаете о других инструментах созданий приложений? Я знаю о таком инструменте как React Native и нагуглить NativeScript, но учитывая свой печальный опыт с React'ом, я боюсь что с ним тоже будет не всё так гладко. Ну конечно же, это ни секрет что JS медленный.
Вы знаете что-нибудь об их этих инструментах и как легко с ними будет работать? После ваших слов, создаётся ощущение, что в случае с Electorn'ом это действительно будет достаточно склонировать репозитирий и собрать из него десктопное приложение - и мне интересно, будет ли здесь всё так же просто?


>> Можно, но мне наоборот кажется это не очень красивым, когда в URL что-то появляется. Хочу чтобы было так.
>
>Так это не сохраняет состояние. Если мы обновим страницу, или используем навигацию в браузере (вперед/назад), то email потеряется. Потому аргументы и хранят в URL.
>
>Тут тогда логичнее было просто сделать единую страницу регистрации/логина на одном роуте с двумя формами и скрывать одну из них. А email хранить в обычной переменной. И не понадобятся такие явно ненадежные костыли.
Мне кажется так удобно будет для каждого пользователя. Например, мы же не можем знать заранее зарегистрирован человек или только хочет зарегистрироваться, и всё что ему нужно это просто ввести свой email и дальше система определит, что он хочет сделать. Я такое видел только в моей бывшей любимой соц.сети - tumblr'е, но сейчас они убрали эту функцию. Я считаю это очень удобным и типо приветливым.


>> Проверить... что, например, вот для серверной части есть проверка, что вернулся StorageWrapper и всё работает, а для браузерной тоже аналогично. Это не нужно?
>
>Проверять это логичнее, пытаясь что-то туда сохранить, а не проверкой, к какому классу относится объект.
Понимаю, исправлю.


>> У роутеров WAMP'а на Node, нету возможностей для аутентифекации и авторизации соединения, и документации. Вышеупомянутая платформа развита лучше других.
>
>Да, но это можно добавить. А в случае с Питоном, ты не сможешь вызывать функции из приложения Node, тебе придется возможно дублировать их, или настраивать взаимодействие. Я не против такого варианта, но готовься к сложностям. И надо правильно спроектировать взаимодействие между питоновским и нодовским приложениями. Это уже почти микросервисы получаются со всеми их плюсами и минусами.
Можно вызывать функции приложения из Node с помощью Remote Procedure Calls паттерна реализуемым WAMP.

Я не знаю, о каких функциях приложения Node вы говорили, но у crossbar.io есть возможность сделать компонент(?) и на другом языке https://crossbar.io/docs/Guest-Configuration/?highlight=node.js