Git Product home page Git Product logo

module-authserver's Introduction

Сервер авторизации

Модуль для Апостол CRM.

Описание

  • Сервер авторизации разработан по стандартам:

    • RFC 6749: The OAuth 2.0 Authorization Framework;
    • OpenID Connect;
    • RFC 7519: JSON Web Token (JWT).
  • Позволяет упростить доступ пользователей к приложениям, разрешив для этого использование существующей учетной записи социальной сети или, например, учетной записи пользователя в Google или в ЕСИА (Госуслуги).

Установка

Следуйте указаниям по сборке и установке Апостол CRM

Документация

Аутентификация и авторизация

Использование протокола OAuth 2.0 для авторизации пользователя

Протокол определяет четыре роли:

  • Владелец ресурса (resource owner) – Пользователь системы (физическое лицо);
  • Клиент (client) – Приложение, которое запрашивает доступ к защищаемому ресурсу от имени его владельца;
  • Сервер авторизации (authorization server) – сервер, который выпускает для клиента маркеры идентификации с разрешениями от владельца ресурса, а также маркеры доступа, позволяющие получать доступ к данным;
  • Поставщик ресурса (resource server) – сервер, обеспечивающий доступ к защищаемому ресурсу на основе проверки маркеров идентификации и маркеров доступа (например, к идентификационным данным пользователя).

В рамках данной системы authorization server и resource server - это один и тот же сервер.

Для взаимодействия Клиента с Сервером необходимо получить идентификатор (client_id) и секрет (client_secret).

Взаимодействие происходит через RESTful API описанное в спецификации RFC 6749.

Использование OpenID Connect для аутентификации пользователя

В общем виде схема аутентификация с использованием OpenID Connect выглядит следующим образом:

  • Клиент (client) готовит запрос на аутентификацию пользователя с необходимыми параметрами;
  • Клиент (client) отправляет GET запрос на аутентификацию в адрес сервера авторизации;
  • Сервер авторизации (authorization server) аутентифицирует пользователя (пользователь вводит логин и пароль);
  • Сервер авторизации (authorization server) получает согласие пользователя на проведение аутентификации в данной системе;
  • Сервер авторизации (authorization server) перенаправляет пользователя обратно Клиенту и передает код авторизации;
  • Клиент (client) отправляет POST запрос с использованием кода авторизации на получения маркера идентификации;
  • Клиент (client) получает ответ, содержащий необходимый маркер идентификации (меняет код авторизации на маркер доступа);
  • Клиент (client) проводит проверку маркера идентификации и извлекает из маркера идентификатор пользователя.

Далее детально будут рассмотрены формируемые Клиентом запросы и ответы от Сервера авторизации.

Конечные точки сервера авторизации (API)

Для авторизации:

GET /oauth2/authorize

Для получения маркера доступа:

POST /oauth2/token

Разрешение на авторизацию

Протокол OAuth 2 определяет четыре разных типа разрешения на авторизацию, каждый из которых полезен в определённых ситуациях:

  1. Код авторизации (Authorization Code): используется с серверными приложениями (server-side applications).
  2. Неявный (Implicit): используется мобильными или веб-приложениями (JavaScript), приложениями работающими на устройстве пользователя.
  3. Учётные данные владельца ресурса (Resource Owner Password Credentials): используются доверенными приложениями, например приложениями, которые являются частью самого сервера.
  4. Учётные данные клиента (Client Credentials): используются при доступе клиента (приложения) к API без авторизации пользователя.

Типы разрешения на авторизацию

Код авторизации

  • Код авторизации является одним из наиболее распространённых типов разрешения на авторизацию, поскольку он хорошо подходит для серверных приложений, где исходный код приложения и секрет клиента не доступны посторонним. Процесс в данном случае строится на перенаправлении (redirection), что означает, что приложение должно быть в состоянии взаимодействовать с пользовательским агентом (user-agent), например, веб-браузером, и получать коды авторизации API, перенаправляемые через пользовательский агент.

Параметры запроса:

Поле Значение Описание
client_id client_id Обязательный. Идентификатор клиента (приложения).
redirect_uri redirect_uri Обязательный. URI, на который сервер авторизации перенаправит агента пользователя (браузер) и код авторизации.
response_type code Обязательный. Указывает на то, что приложение запрашивает доступ с помощью кода авторизации.
scope scope Рекомендуемый. Список областей, разделенных пробелами, которые определяют ресурсы, к которым ваше приложение может получить доступ от имени пользователя.
access_type access_type Рекомендуемый. Указывает, может ли ваше приложение обновлять маркеры доступа, когда пользователь отсутствует в браузере. Допустимые значения параметров: online (по умолчанию) и offline.
state state Рекомендуемый. Набор случайных символов которые будут возвращены сервером клиенту (используется для защиты от повторных запросов).

Пример зпроса:

GET /oauth2/authorize?client_id=YOUR-CLIENT-ID&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth2%2Fcode&scope=api&response_type=code&access_type=online&state=c2FmZXR HTTP/1.1
Host: localhost:8080
http://localhost:8080/oauth2/authorize?
  client_id=YOUR-CLIENT-ID&
  redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth2%2Fcode&
  response_type=code&
  access_type=online&
  scope=api&
  state=c2FmZXR

Если в ходе аутентификации не возникло ошибок, то сервер авторизации перенаправит пользователя по ссылке, указанной в redirect_uri, а также вернёт два обязательных параметра:

  • code – Код авторизации;
  • state – Значение параметра state, которое было получено в запросе на аутентификацию;

Клиент должен провести сравнение отправленного и полученного параметра state.

Ответ с кодом авторизации:

http://localhost:8080/oauth2/code?code=b%2F8NpjbB4eLaukGr68tE7maTCeBISO%2FC7hWxKGuKb8I4Ysc7uw8a2MRUMWnO3Nzt
  • Обратите внимание, на то что код (b/8NpjbB4eLaukGr68tE7maTCeBISO/C7hWxKGuKb8I4Ysc7uw8a2MRUMWnO3Nzt) закодирован алгоритмом URL encode и его нужно будет декодировать алгоритмом URL Decode.

Если в ходе аутентификации возникла ошибка, то сервер авторизации перенаправит пользователя по ссылке, указанной в redirect_uri с информацией об ошибке:

http://localhost:8080/oauth2/code?code=403&error=access_denied&error_description=Access%20denied.

Для обмена кода авторизации на маркер доступа Клиент должен сформировать запрос методом POST.

Параметры запроса:

Поле Значение Описание
client_id client_id Обязательный. Идентификатор клиента.
client_secret client_secret Обязательный. Секрет клиента.
grant_type authorization_code Обязательный. Как определено в спецификации OAuth 2.0, это поле должно содержать значение authorization_code.
code code Обязательный. Код авторизации, возвращенный из первоначального запроса.
redirect_uri redirect_uri Обязательный. URI переадресации (должен совпадать с redirect_uri из первоначального запроса).
  • Согласно спецификации OAuth 2.0, параметры авторизации клиента (client_id и client_secret) могут быть переданы как в теле запроса так и в HTTP заголовке Authorization (HTTP Basic authentication).
Authorization: Basic d2ViLXNlcnZpY2UucnU6Y2xpZW50IHNlY3JldA==   

В ответ на запрос сервер авторизации, вернет объект JSON, который содержит маркер краткосрочного доступа и маркер обновления.

Ответ содержит следующие поля:

Поле Тип Описание
access_token STRING Маркер краткосрочного доступа (сроком действия 1 час).
expires_in INTEGER Оставшееся время жизни маркера доступа в секундах.
token_type STRING Тип возвращаемого маркера. Значение всегда будет Bearer.
session STRING Идентификатор сессии пользователя.
refresh_token STRING * Маркер который вы можете использовать для получения нового маркер доступа.
id_token STRING * Маркер пользователя.
  • Обратите внимание, что маркер обновления возвращается только в том случае, если ваше приложение в первоначальном запросе к серверу авторизации, установило в access_type значение: offline.
  • Обратите внимание, что маркер пользователя возвращается только в том случае, если ваше приложение в первоначальном запросе к серверу авторизации, установило в scope одно из значений: openid, profile или email.

Пример зпроса:

POST http://localhost:8080/oauth2/token
Content-Type: application/x-www-form-urlencoded
 
client_id=YOUR-CLIENT-ID&
client_secret=YOUR-CLIENT-SECRET&
grant_type=authorization_code&
code=b%2F8NpjbB4eLaukGr68tE7maTCeBISO%2FC7hWxKGuKb8I4Ysc7uw8a2MRUMWnO3Nzt&
redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth2%2Fcode
* Хоть это и не определено спецификацией, но сервер авторизации примет запрос и в формате JSON (Content-Type: application/json)

Пример ответа:

{
  "access_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiIDogImFjY291bnRzLnNoaXAtc2FmZXR5LnJ1IiwgImF1ZCIgOiAid2ViLXNoaXAtc2FmZXR5LnJ1IiwgInN1YiIgOiAiZGZlMDViNzhhNzZiNmFkOGUwZmNiZWYyNzA2NzE3OTNiODZhYTg0OCIsICJpYXQiIDogMTU5MzUzMjExMCwgImV4cCIgOiAxNTkzNTM1NzEwfQ.NorYsi-Ht826HUFCEArVZ60_dEUmYiJYXubnTyweIMg",
  "token_type" : "Bearer",
  "expires_in" : 3600,
  "session" : "dfe05b78a76b6ad8e0fcbef270671793b86aa848"
}

Неявный

  • Неявный тип разрешения на авторизацию используется мобильными и веб-приложениями (приложениями, которые работают в веб-браузере - JavaScript), где конфиденциальность секрета клиента не может быть гарантирована. Неявный тип разрешения также основан на перенаправлении пользовательского агента, при этом маркер доступа передаётся пользовательскому агенту для дальнейшей передачи приложению. Это, в свою очередь, делает маркер доступным пользователю и другим приложениям на устройстве пользователя. Также при этом типе разрешения на авторизацию не осуществляется аутентификация подлинности приложения, а сам процесс полагается на URI перенаправления (зарегистрированном ранее в сервере авторизации).

  • Неявный тип разрешения на авторизацию не поддерживает маркеры обновления (refresh_token) и маркера пользователя (id_token).

Параметры запроса:

Поле Значение Описание
client_id client_id Обязательный. Идентификатор клиента (приложения).
redirect_uri redirect_uri Обязательный. URI, на который сервер авторизации перенаправит агента пользователя (браузер) и маркер доступа.
response_type token Обязательный. Приложения JavaScript должны установить значение параметра в token. Это значение указывает серверу авторизации возвращать маркер доступа в виде пары name = value в идентификаторе фрагмента URI (#), на который перенаправляется пользователь после завершения процесса авторизации.
scope scope Рекомендуемый. Список областей, разделенных пробелами, которые определяют ресурсы, к которым ваше приложение может получить доступ от имени пользователя.
state state Рекомендуемый. Набор случайных символов которые будут возвращены сервером клиенту (используется для защиты от повторных запросов).

Пример зпроса:

GET /oauth2/authorize?client_id=YOUR-CLIENT-ID&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth2%2Fcallback&scope=api&response_type=token&state=c2FmZXR HTTP/1.1
Host: localhost:8080
http://localhost:8080/oauth2/authorize?
  client_id=YOUR-CLIENT-ID&
  redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth2%2Fcallback&
  response_type=token&
  scope=api&
  state=c2FmZXR

Маркер доступа или сообщение об ошибке возвращаются во фрагменте хэша URI перенаправления, как показано ниже:

Ответ с маркером доступа:

http://localhost:8080/callback#token_type=Bearer&expires_in=3600&session=dfe05b78a76b6ad8e0fcbef270671793b86aa848&access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiIDogImFjY291bnRzLnNoaXAtc2FmZXR5LnJ1IiwgImF1ZCIgOiAid2ViLXNoaXAtc2FmZXR5LnJ1IiwgInN1YiIgOiAiZGZlMDViNzhhNzZiNmFkOGUwZmNiZWYyNzA2NzE3OTNiODZhYTg0OCIsICJpYXQiIDogMTU5MzUzMjExMCwgImV4cCIgOiAxNTkzNTM1NzEwfQ.NorYsi-Ht826HUFCEArVZ60_dEUmYiJYXubnTyweIMg
  • В дополнение к параметру access_token строка фрагмента также содержит параметр token_type, который всегда имеет значение Bearer, и параметр expires_in, который указывает время жизни маркера в секундах. Если параметр state был указан в запросе маркера доступа, его значение также включается в ответ.

  • URI перенаправления, в данном случае это http://localhost:8080/callback/index.html должен указывать на веб-страницу, которая содержит скрипт для извлечения маркера доступа из URI перенаправления.

Ответ с ошибкой:

http://localhost:8080/callback#code=403&error=access_denied&error_description=Access%20denied.
Сервер авторизации поддерживает гибридный режим типов разрешения. Если в параметре response_type указать, через пробел, оба значения code token, то сервер авторизации вернет в одном запросе и код авторизации и маркер доступа.

Учётные данные владельца ресурса

  • При этом типе разрешения на авторизацию пользователь предоставляет приложению напрямую свои учётные данные (имя пользователя и пароль). Приложение, в свою очередь, использует полученные учётные данные пользователя для получения маркера доступа от сервера авторизации. Этот тип разрешения на авторизацию должен использоваться только в том случае, когда другие варианты не доступны. Кроме того, этот тип разрешения стоит использовать только в случае, когда приложение пользуется доверием пользователя (например, является частью самой системы).

После того, как пользователь передаст свои учётные данные приложению, приложение запросит маркер доступа у авторизационного сервера методом POST.

Параметры запроса:

Поле Значение Описание
client_id client_id Обязательный. Идентификатор клиента.
client_secret client_secret Обязательный. Секрет клиента.
grant_type password Обязательный. Как определено в спецификации OAuth 2.0, это поле должно содержать значение password.
username username Вариативный. Логин пользователя. Игнорируется если указано значение в поле secret.
password password Вариативный. Пароль пользователя. Игнорируется если указано значение в поле secret.
secret secret Вариативный. Секретный код. Если значение указано то поля username и password заполнять не нужно.
scope scope Рекомендуемый. Список областей, разделенных пробелами, которые определяют ресурсы, к которым ваше приложение может получить доступ от имени пользователя.
  • Согласно спецификации OAuth 2.0, параметры авторизации клиента (client_id и client_secret) могут быть переданы как в теле запроса так и в HTTP заголовке Authorization (HTTP Basic authentication).
Authorization: Basic d2ViLXNlcnZpY2UucnU6Y2xpZW50IHNlY3JldA==   

В ответ на запрос сервер авторизации, вернет объект JSON, который содержит маркер краткосрочного доступа и маркер обновления.

Ответ содержит следующие поля:

Поле Тип Описание
access_token STRING Маркер краткосрочного доступа (сроком действия 1 час).
expires_in INTEGER Оставшееся время жизни маркера доступа в секундах.
token_type STRING Тип возвращаемого маркера. Значение всегда будет Bearer.
session STRING Идентификатор сессии пользователя.
refresh_token STRING Маркер который вы можете использовать для получения нового маркер доступа.
id_token STRING * Маркер пользователя.
  • Обратите внимание, что маркер пользователя возвращается только в том случае, если ваше приложение в запросе к серверу авторизации, установило в scope одно из значений: openid, profile или email.

Пример зпроса:

POST http://localhost:8080/oauth2/token
Content-Type: application/x-www-form-urlencoded
 
client_id=YOUR-CLIENT-ID&
client_secret=YOUR-CLIENT-SECRET&
grant_type=password&
username=admin&
password=admin
* Хоть это и не определено спецификацией, но сервер авторизации примет запрос и в формате JSON (Content-Type: application/json)

Если учётные данные и клиента и пользователя корректны, сервер авторизации вернёт маркер доступа для приложения.

Учётные данные клиента

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

Параметры запроса:

Поле Значение Описание
client_id client_id Обязательный. Идентификатор клиента.
client_secret client_secret Обязательный. Секрет клиента.
grant_type client_credentials Обязательный. Как определено в спецификации OAuth 2.0, это поле должно содержать значение client_credentials.
scope scope Рекомендуемый. Список областей, разделенных пробелами, которые определяют ресурсы, к которым ваше приложение может получить доступ от имени пользователя.
  • Согласно спецификации OAuth 2.0, параметры авторизации клиента (client_id и client_secret) могут быть переданы как в теле запроса так и в HTTP заголовке Authorization (HTTP Basic authentication).
Authorization: Basic d2ViLXNlcnZpY2UucnU6Y2xpZW50IHNlY3JldA==   

В ответ на запрос сервер авторизации, вернет объект JSON, который содержит маркер краткосрочного доступа и маркер обновления.

Ответ содержит следующие поля:

Поле Тип Описание
access_token STRING Маркер доступа (сроком действия 1 день).
expires_in INTEGER Оставшееся время жизни маркера доступа в секундах.
token_type STRING Тип возвращаемого маркера. Значение всегда будет Bearer.
session STRING Идентификатор сессии пользователя.
refresh_token STRING Маркер который вы можете использовать для получения нового маркер доступа.
id_token STRING * Маркер пользователя.
  • Обратите внимание, что маркер пользователя возвращается только в том случае, если ваше приложение в запросе к серверу авторизации, установило в scope одно из значений: openid, profile или email.

Пример зпроса:

POST http://localhost:8080/oauth2/token
Content-Type: application/x-www-form-urlencoded
 
client_id=YOUR-CLIENT-ID&
client_secret=YOUR-CLIENT-SECRET&
grant_type=client_credentials

Обновление маркера доступа

  • После истечения срока действия маркера доступа все запросы к API с его использованием будут возвращать ошибку с кодом 403 ("Token expired"). Если при создании маркера доступа был создан и маркер для обновления маркера доступа (refresh_token), последний может быть использован для получения нового маркера доступа от сервера авторизации.

Параметры запроса:

Поле Значение Описание
client_id client_id Обязательный. Идентификатор клиента.
client_secret client_secret Обязательный. Секрет клиента.
grant_type refresh_token Обязательный. Как определено в спецификации OAuth 2.0, это поле должно содержать значение refresh_token.
refresh_token refresh_token Обязательный. Маркер обновления, выданный ранее.
scope scope Рекомендуемый. Список областей, разделенных пробелами, которые определяют ресурсы, к которым ваше приложение может получить доступ от имени пользователя.
  • Согласно спецификации OAuth 2.0, параметры авторизации клиента (client_id и client_secret) могут быть переданы как в теле запроса так и в HTTP заголовке Authorization (HTTP Basic authentication).
Authorization: Basic d2ViLXNlcnZpY2UucnU6Y2xpZW50IHNlY3JldA==   

Пример зпроса:

POST http://localhost:8080/oauth2/token
Content-Type: application/x-www-form-urlencoded
 
client_id=YOUR-CLIENT-ID&
client_secret=YOUR-CLIENT-SECRET&
grant_type=refresh_token&
refresh_token=e%2FdtGmXCIzHvPMURn%2FTH9udTPxtKpR5FFifx2uvH1WqT4myXLtgyjkLgYDy7g3Ik5MrFRR82
* Хоть это и не определено спецификацией, но сервер авторизации примет запрос и в формате JSON (Content-Type: application/json)

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

Грант на продление

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

Параметры запроса:

Поле Значение Описание
client_id client_id Обязательный. Идентификатор клиента.
client_secret client_secret Обязательный. Секрет клиента.
grant_type urn:ietf:params:oauth:grant-type:token-exchange Обязательный. Как определено в спецификации, это поле должно содержать значение urn:ietf:params:oauth:grant-type:token-exchange.
subject_token subject_token Обязательный. Маркер, выданный ранее.
subject_token_type subject_token_type Рекомендуемый. Тип передаваемого маркера. Доступные значения: urn:ietf:params:oauth:token-type:jwt (по умолчанию), urn:ietf:params:oauth:token-type:access_token, urn:ietf:params:oauth:token-type:refresh_token, urn:ietf:params:oauth:token-type:id_token.
scope scope Рекомендуемый. Список областей, разделенных пробелами, которые определяют ресурсы, к которым ваше приложение может получить доступ от имени пользователя.
  • Согласно спецификации OAuth 2.0, параметры авторизации клиента (client_id и client_secret) могут быть переданы как в теле запроса так и в HTTP заголовке Authorization (HTTP Basic authentication).
Authorization: Basic d2ViLXNlcnZpY2UucnU6Y2xpZW50IHNlY3JldA==   

Пример зпроса:

POST http://localhost:8080/oauth2/token
Content-Type: application/x-www-form-urlencoded
 
client_id=YOUR-CLIENT-ID&
client_secret=YOUR-CLIENT-SECRET&
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange&
subject_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.[сокращенно для краткости].NorYsi-Ht826HUFCEArVZ60_dEUmYiJYXubnTyweIMg&
subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Ajwt
* Хоть это и не определено спецификацией, но сервер авторизации примет запрос и в формате JSON (Content-Type: application/json)

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

Использование маркера JWT в качестве гранта авторизации

  • Данный тип гранта позволяет авторизоваться в системе по данным из JWT маркера выпущенного другой (внешней) системой, например Google.

Параметры запроса:

Поле Значение Описание
client_id client_id Обязательный. Идентификатор клиента.
client_secret client_secret Обязательный. Секрет клиента.
grant_type urn:ietf:params:oauth:grant-type:jwt-bearer Обязательный. Это поле должно содержать значение urn:ietf:params:oauth:grant-type:jwt-bearer.
assertion assertion Обязательный. Маркер JWT выданный другой (внешней) системой.
  • Согласно спецификации OAuth 2.0, параметры авторизации клиента (client_id и client_secret) могут быть переданы как в теле запроса так и в HTTP заголовке Authorization (HTTP Basic authentication).
Authorization: Basic d2ViLXNlcnZpY2UucnU6Y2xpZW50IHNlY3JldA==   

Пример зпроса:

POST http://localhost:8080/oauth2/token
Content-Type: application/x-www-form-urlencoded
 
client_id=YOUR-CLIENT-ID&
client_secret=YOUR-CLIENT-SECRET&
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&
assertion=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.[сокращенно для краткости].NorYsi-Ht826HUFCEArVZ60_dEUmYiJYXubnTyweIMg

module-authserver's People

Contributors

ufocomp avatar

Stargazers

 avatar  avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.