Code Monkey home page Code Monkey logo

nginx-google-oauth's Introduction

nginx-google-oauth

Lua module to add Google OAuth to nginx. It is based on great work from Agora Games.

Fast forward to the docker image section to try it out.

Deprecated

This project is no longer being maintained or supported by Cloudflare. Please refer to Cloudflare Access as an alternative to nginx-google-oauth.

Installation

You can copy access.lua to your nginx configurations, or clone the repository. Your installation of nginx must already be built with Lua support, and you will need the following modules:

Configuration

Add the access controls in your configuration. Because OAuth tickets will be included in cookies (and you are presumably protecting something very important), it is strongly recommended that you use SSL.

server {
  server_name supersecret.net;

  listen 443 ssl;

  ssl_certificate     /etc/nginx/certs/supersecret.net.pem;
  ssl_certificate_key /etc/nginx/certs/supersecret.net.key;

  set $ngo_client_id         "abc-def.apps.googleusercontent.com";
  set $ngo_client_secret     "abcdefg-123-xyz";
  set $ngo_token_secret      "a very long randomish string";
  set $ngo_secure_cookies    "true";
  set $ngo_http_only_cookies "true";

  access_by_lua_file "/etc/nginx/lua/nginx-google-oauth/access.lua";
}

The access controls can be configured using nginx variables. The supported variables are:

  • $ngo_callback_host The host for the callback, defaults to first entry in the server_name list (e.g supersecret.net).
  • $ngo_callback_scheme The scheme for the callback URL, defaults to that of the request (e.g. https).
  • $ngo_callback_uri The URI for the callback, defaults to /_oauth.
  • $ngo_signout_uri The URI for sign-out endpoint.
  • $ngo_client_id This is the client id key.
  • $ngo_client_secret This is the client secret.
  • $ngo_token_secret The key used to encrypt the session token stored in the user cookie. Should be long & unguessable.
  • $ngo_secure_cookies If defined, will ensure that cookies can only be transferred over a secure connection.
  • $ngo_http_only_cookies If defined, will ensure that cookies cannot be accessed via javascript.
  • $ngo_extra_validity Time in seconds to add to token validity period.
  • $ngo_domain The space separated list of domains to use for validating users when not using white- or blacklists.
  • $ngo_whitelist Optional space separated list of authorized email addresses.
  • $ngo_blacklist Optional space separated list of unauthorized email addresses.
  • $ngo_user If set, will be populated with the OAuth username returned from Google (portion left of '@' in email).
  • $ngo_email_as_user If set and $ngo_user is defined, username returned will be full email address.

Available endpoints

/_signout

Default sign-out URI, can be changed with $ngo_signout_uri variable. It clears cookies and does redirect to the / location of your domain.

/_token.json

Endpoint that reports your OAuth token in a JSON object:

{
  "email": "[email protected]",
  "token": "abc..xyz",
  "expires": 1445455680
}

/_token.txt

Endpoint that reports your OAuth token in text format:

email: [email protected]
token: abc..xyz
expires: 1445455680

/_token.curl

Endpoint that reports your OAuth token as curl arguments for header auth:

-H "OauthEmail: [email protected]" -H "OauthAccessToken: abc..xyz" -H "OauthExpires: 1445455680"

You can add it to your curl command to make it work with OAuth.

Authentication

Any request to nginx can be authenticated in two ways: with headers and with cookies. When you open your site in a web browser, it sends you to Google to obtain OAuth token and these are set as cookies. Users don't have to do anything special, it just works seamlessly.

If you are willing to protect a domain that is used by automatic CLI tools, it is problematic to use cookies from your browser. Instead, you can can use any of endpoints described in the previous section to obtain tokens and pass them to your tools.

An example would be a curl command that you might use to refresh local currency rates:

curl -s https://example.com/rates.json > ~/currency-rates.json

Now if you enabled OAuth on example.com, this command would not work anymore, resulting in 301 redirect to OAuth from Google. To make it work, you'll have to go to https://example.com/_token.curl, copy header arguments for curl and paste them into your command:

curl -s $HEADER_ARGS https://example.com/rates.json > ~/currency-rates.json

Extended token validity

OAuth token from Google are short-lived, but this is not always convenient if you want to put something frequently used behind OAuth. In this case, you can extend token validity by $ngo_extra_validity seconds. An good example would be some site you use at work. Setting $ngo_extra_validity to 43200 (12h) means that you only have to authorize on it once a day with a standard 8 hour or less work day.

Token validity can be shortened with negative values as well.

Configuring OAuth access

Visit https://console.developers.google.com. If you're signed in to multiple Google accounts, be sure to switch to the one which you want to host the OAuth credentials (usually your company's Apps domain). This should match $ngo_domain (e.g. yourcompany.com).

From the dashboard, create a new project. After selecting that project, search for "Credentials" in the search box. Make sure to fill "OAuth consent screen" section first. Then create "OAuth client ID": select "Web application", fill the name of your app, skip "Authorized JavaScript origins" and fill "Authorized redirect URIs" (e.g. https://example.com/_oauth).

After completing the form you will be presented with the Client ID and Client Secret which you can use to configure $ngo_client_id and $ngo_client_secret respectively.

Since you can have unlimited OAuth client IDs in one app, but number of apps is limited, it makes sense to reuse the same app.

Username variable

$ngo_user can be used in any place where you could use variable in nginx, this includes:

  • Logging
  • Passing params to external FastCGI/UWSGI scripts
  • Headers for upstream servers
  • Lua scripts
  • etc.

Blacklist/Whitelist

For blacklist the site, not even reaching oauth, use this nginx example:

    access_by_lua_file "/etc/nginx/lua/nginx-google-oauth/access.lua";
    deny your_blacklist_ip;
    satisfy all

For whitelist (ie: disable oauth for this ip) use this:

    access_by_lua_file "/etc/nginx/lua/nginx-google-oauth/access.lua";
    allow your_whitelist_ip;
    satisfy any;

Notice the satisfy any. You can also add several allow entries.

For allowing only one ip, block all others, and still oauth it, use this:

    access_by_lua_file "/etc/nginx/lua/nginx-google-oauth/access.lua";
    allow your_whitelist_ip;
    deny all;
    satisfy all;

Docker image

You have to obtain tokens first.

There is a pre-built image: cloudflare/nginx-google-oauth. If you are hacking on this project, you might want to rebuild the image yourself.

To make it work locally, add a record to DNS or to /etc/hosts, pointing to the ip of your docker daemon, we use ngo.lol here. Make sure to add http://ngo.lol/_oauth as an "Authorized redirect URIs" in Google console.

Docker image has the following env variables for configuration:

  • NGO_CALLBACK_HOST is the value of $ngo_callback_host.
  • NGO_CALLBACK_SCHEME is the value of $ngo_callback_scheme.
  • NGO_CALLBACK_URI is the value of $ngo_callback_uri.
  • NGO_SIGNOUT_URI is the value of $ngo_signout_uri.
  • NGO_CLIENT_ID is the value of $ngo_client_id, required.
  • NGO_CLIENT_SECRET is the value of $ngo_client_secret, required.
  • NGO_TOKEN_SECRET is the value of $ngo_token_secret, required.
  • NGO_SECURE_COOKIES is the value of $ngo_secure_cookies.
  • NGO_HTTP_ONLY_COOKIES is the value of $ngo_http_only_cookies.
  • NGO_EXTRA_VALIDITY is the value of $ngo_extra_validity.
  • NGO_DOMAIN is the value of $ngo_domain.
  • NGO_WHITELIST is the value of $ngo_whitelist.
  • NGO_BLACKLIST is the value of $ngo_blacklist.
  • NGO_USER is the value of $ngo_user.
  • NGO_EMAIL_AS_USER is the value of $ngo_email_as_user.
  • PORT is the port for nginx to listen on, defaults to 80.
  • DEBUG: if set, prints the generated nginx configuation on container start
  • LOCATIONS can contain location directives that will be injected into the nginx configuration on container start. If not set, a demo page is served under location / {...}.

Run the image:

docker run --rm -it --net host \
  -e DEBUG=1 \
  -e NGO_CALLBACK_SCHEME=http \
  -e NGO_CLIENT_ID="client id from google" \
  -e NGO_CLIENT_SECRET="client secret from google" \
  -e NGO_TOKEN_SECRET="random token secret" \
  cloudflare/nginx-google-oauth:1.1.1

Then open your browser at http://ngo.lol and you should get Google OAuth screen.

Copyright

  • Copyright 2015-2020 Cloudflare
  • Copyright 2014-2015 Aaron Westendorf

License

MIT

nginx-google-oauth's People

Contributors

alekseyp avatar bcaller avatar bobrik avatar damireh avatar danielmotaleite avatar davidxia avatar dhaynespls avatar gabrieloshiro avatar jayniz avatar maxnaude avatar riking avatar seelmann avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

nginx-google-oauth's Issues

/_signout not working in Chrome but does work in other browsers

Thank you for your docker image. I'm a complete novice to OAuth, but with your image I was able to successfully deploy a secured landing page and reverse proxy two web apps (that are in turn now also secured with Google OAuth).

I'm experiencing an odd problem and have no idea how to troubleshoot it. The signout URI works successfully in three different browsers (Firefox & Safari on macOS and Safari on iOS) but effectively does nothing in Chrome (macOS).

When following the /_signout URL in Chrome, the login token remains current and / simply reloads, still logged in.

It's hard to imagine that Chrome itself is the problem as the transaction is straightforward and should trigger the appropriate actions in the Docker image. And, yet, it's the only browser in which this does not work.

Any ideas?

110: Operation timed out

Tried putting resolver 8.8.8.8 in nginx conf (not sure why ipv6=off is not working)

2016/12/08 15:41:44 [error] 4802#0: *344 [lua] access.lua:202: authorize(): got error during access token request: accounts.google.com could not be resolved (110: Operation timed out)

Please help.

Multiple emails for $ngo_whitelist

I'm trying to have more than one email in the whitelist, but I don't now how to format the list. Try comas & whitespace but no luck.

I did look at the source, but I don't really speak Lua, so it was unclear if there was a solution for this.

cannot log in after 'signing out' if whitelist set

I had been running without NGO_WHITELIST set, so while it required a user to login through google, there was no limiting who could log in. Today, I set NGO_WHITELIST to include a particular email account, and I was able to continue viewing contents. As a test, I signedout (_signout). Afterwards 403 errors, and there is no way to sign back in , so long as I have NGO_WHITELIST set. I have to stop my container, restart it without NGO_WHITELIST, (get credentials again), then restop the container and then restart it again with the NGO_WHITELIST value set.

Unfortunately this means the current behavior makes it impossible for me (or any other user) to make use of our websites, as its not possible to 'log in' unless you're already logged in.

Runtime Error : index upvalue 'domain' (a nil value)

I've installed the loa module on my raspberry pi 1 which i'm using as a reverse proxy.

I've tried to protect one of my locations via the oauth2 module:

  location ~ ^/(ifttt|_oauth) {
      set $ngo_client_id         "123demo.apps.googleusercontent.com";
      set $ngo_client_secret     "secret";
      set $ngo_token_secret      "mysecret";
      set $ngo_secure_cookies    "true";
      set $ngo_http_only_cookies "true";
      access_by_lua_file "/etc/nginx/lua/access.lua";
  }

And added the resolver and ssl config for lua.

After getting the request in /_oauth i get a returncode 500 and this error in the errorlog:

error] 4207#4207: *4 lua entry thread aborted: runtime error: /etc/nginx/lua/access.lua:64: attempt to index upvalue 'domain' (a nil value)
stack traceback:
coroutine 0:
        /etc/nginx/lua/access.lua: in function 'check_domain'
        /etc/nginx/lua/access.lua:92: in function 'on_auth'
        /etc/nginx/lua/access.lua:234: in function 'authorize'

I've also tried to specify a domain, but same result.
set $ngo_domainn "gmail.com";

Is this a config problem or a bug?

How to provide custom locations

Hello,

I would like to provide custom location directives such as below but I do not understand how to provide them within the environment variable LOCATIONS. Would you have a small example?

location / {
  proxy_pass http://node:3000/;
}

Thanks!

Getting 403

Hi, thanks for this piece of work. It seems to be exactly waht I'm looking for.
Unfortunately I cannot get it to work.
I get the redirect to accounts.google.com but on the way back it seems to crash (sorry for not being more precise...)

I get an 403 and this in the logs

ccess.lua:202: authorize(): got error during access token request: no resolver defined to resolve "accounts.google.com", ...

I've got no idea what that means.

Error access.lua recived temp failure in name resolution

I configured a nginx reverse proxy and everything worked ok when I tested in a standard web page but when i try to make it work to an openstack dashboard, afer logon I'm getting a 401 Authorization Required

error.log file

2018/07/27 09:25:55 [error] 1311#0: *1 [lua] access.lua:103: received temporary failure in name resolution from https://accounts.google.com/o/oauth2/token, client: 10.10.1.10, server: openstack.domain.com, request: "GET /_oauth?state=https%3A%2F%2Fopenstack.domain.com%2F&code=4%2FAADynPaXKhvDJ_4bfB88kiO-OC_G0DWtjXjG1i-XY7sKou-5KF6i26Q39Su1CHHU_y-R1ls0qYqylFKbUwqTmfw HTTP/1.1", host: "openstack.domain.com", referrer: "https://accounts.google.es/"

oauth.conf file

some parts are oculted to security

worker_processes  1;

events {
    worker_connections  1024;
}

 http {
    server {
      server_name openstack.domain.com;
      listen 443;

      ssl on;
      ssl_certificate XXXXXX;
      ssl_certificate_key XXXXX;

      set $ngo_client_id "ID";
      set $ngo_client_secret "secret";
      set $ngo_secure_cookies "true";
      set $ngo_token_secret "secret";
      set $callback_host "openstack.domain.com";
      set $ngo_whitelist "whitelisted users";

      lua_code_cache off;
      access_by_lua_file "../access.lua";

#     header_filter_by_lua "ngx.header.content_length = nil";
#      body_filter_by_lua_file "../body_filter.lua";

  location / {
    header_filter_by_lua "ngx.header.content_length = nil";
    body_filter_by_lua_file "/etc/nginx/nginx-google-oauth/body_filter.lua";

    proxy_set_header Accept-Encoding "";
    proxy_pass https://openstack.domain.com/;

    }
}
}

There is something i missed?

Error when configuring SSL through Docker image

Hello,

When I use the Docker image for nginx-google-oauth, everything works fine when I do not use SSL. However, when I use SSL, I see the error:

2017/08/12 02:12:41 [error] 8#0: *1 lua ssl certificate verify error: (20: unable to get local issuer certificate), client: IP_HERE, server: , request: "GET /_oauth?state=httpsURL_HERE&code=THE_CODE HTTP/1.1", host: "MY_HOST", referrer: "https://accounts.google.com/CheckCookie"

I have set the PORT environment variable to 443 ssl, as just 443 does not enable SSL. I am also using a custom version of /etc/nginx/sites-available/default, which is below:

lua_package_path '/etc/nginx/lua/?.lua;';

server {
    listen %port%;

    resolver 8.8.8.8 ipv6=off;

    lua_ssl_trusted_certificate /etc/nginx/certs/SOMETHING.ca.crt;
    lua_ssl_verify_depth        5;
    # TODO: add ssl_certificate and ssl_certificate_key here

    ssl_certificate     /etc/nginx/certs/SOMETHING.pem;
    ssl_certificate_key /etc/nginx/certs/SOMETHING.key;


    error_log /dev/stderr notice;
    access_log /dev/stdout;

    set_by_lua $ngo_callback_host '
      if os.getenv("NGO_CALLBACK_HOST") then
        return os.getenv("NGO_CALLBACK_HOST")
      else
        return ngx.var.host
      end
    ';

    set_by_lua $ngo_callback_scheme    'return os.getenv("NGO_CALLBACK_SCHEME")';
    set_by_lua $ngo_callback_uri       'return os.getenv("NGO_CALLBACK_URI")';
    set_by_lua $ngo_signout_uri        'return os.getenv("NGO_SIGNOUT_URI")';
    set_by_lua $ngo_client_id          'return os.getenv("NGO_CLIENT_ID")';
    set_by_lua $ngo_client_secret      'return os.getenv("NGO_CLIENT_SECRET")';
    set_by_lua $ngo_token_secret       'return os.getenv("NGO_TOKEN_SECRET")';
    set_by_lua $ngo_secure_cookies     'return os.getenv("NGO_SECURE_COOKIES")';
    set_by_lua $ngo_http_only_cookies  'return os.getenv("NGO_HTTP_ONLY_COOKIES")';
    set_by_lua $ngo_extra_validity     'return os.getenv("NGO_EXTRA_VALIDITY")';
    set_by_lua $ngo_domain             'return os.getenv("NGO_DOMAIN")';
    set_by_lua $ngo_whitelist          'return os.getenv("NGO_WHITELIST")';
    set_by_lua $ngo_blacklist          'return os.getenv("NGO_BLACKLIST")';
    set_by_lua $ngo_user               'return os.getenv("NGO_USER")';
    set_by_lua $ngo_email_as_user      'return os.getenv("NGO_EMAIL_AS_USER")';

    access_by_lua_file "/etc/nginx/lua/nginx-google-oauth/access.lua";

    expires 0;

    add_header Google-User $ngo_user;

    include /etc/nginx/snippets/demo-locations.conf;
}

I merely changed it to include the ssl_certificate and ssl_certificate_key statements. My Dockerfile adds SOMETHING.ca.crt, SOMETHING.pem, and SOMETHING.key to the /etc/nginx/certs/ directory. Please let me know if you see any issues, or if I need to do anything else. I have also tried not including the ssl_certificate or the ssl_certificate_key statements, but this did not work either.

attempt to concatenate local 'cb_server_name' (a nil value)

Hello,

I followed all steps in order to make it to work and everything was just fine.

After I added the configuration text to my default.conf file I'm getting the following error when I visit the page in my browser.

error.log

017/04/10 15:36:12 [error] 26805#0: *1 lua entry thread aborted: runtime error: /usr/local/openresty/nginx/conf/access.lua:18: attempt to concatenate local 'cb_server_name' (a nil value)
stack traceback:
coroutine 0:
	/usr/local/openresty/nginx/conf/access.lua: in function </usr/local/openresty/nginx/conf/access.lua:1>, client: 190.215.222.225, server: _, request: "GET / HTTP/1.1", host: "34.207.73.69"

Here's the whole configuration file:

server {
    # Listen on port 80.
    listen 80 default_server;
    listen [::]:80 default_server;

    # The document root.
    root /usr/local/openresty/nginx/html/default;

    # Add index.php if you are using PHP.
    index index.html index.htm;

    # The server name, which isn't relevant in this case, because we only have one.
    server_name _;

    ### CUSTOM: Nginx Google Auth
    set $ngo_client_id      "-removed-";
    set $ngo_client_secret  "-removed-";
    set $ngo_token_secret   “-removed-”;
    set $ngo_secure_cookies "true";

    access_by_lua_file "/usr/local/openresty/nginx/conf/access.lua";
    ### CUSTOM: Nginx Google Auth

    # When we try to access this site...
    location / {
        # ... first attempt to serve request as file, then as a directory,
        # then fall back to displaying a 404.
        try_files $uri $uri/ =404;
    }
   
   # Reverse proxy for Jenkins URI.
    location /jenkins {
        proxy_pass  http://172.31.39.122:8081;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

I would appreciate any help, thanks.

logout not working

Behavior is similar to #31 but I have this problem on both Chrome and Firefox and I've turned on dev tools to disable caching and no change.

When I first go to the nginx website using this module, it asks me to login via google but once I do that, I stay logged on forever (days). This is true on Chrome and Firefox. I haven't set the $ngo_extra_validity so I don't know why my cookie hasn't expired. I then added a href link to _signout and when I click that, I get a 302 for that _signout , then a 302 to / then a 302 to accounts.google.com and then a 302 back to my domain with _oauth?state=blah and finally a 200 to my root page. But no logout.

I'm not using the docker image, but installed this directly on my Raspberry Pi.

Thanks,

SSL Handshake error

It is throwing an error while connecting to accounts.google.com

lua entry thread aborted: runtime error: /usr/local/share/lua/5.1/resty/http.lua:136: attempt to call method 'sslhandshake' (a nil value)
stack traceback:
coroutine 0:
/usr/local/share/lua/5.1/resty/http.lua:136: in function 'ssl_handshake'
/usr/local/share/lua/5.1/resty/http.lua:789: in function 'request_uri'
/etc/nginx/auth/access.lua:105: in function 'request_access_token'
/etc/nginx/auth/access.lua:203: in function 'authorize'

Using $ngo_whitelist and $ngo_blacklist is not possible when using $ngo_domain

Our organization has its own Google domain for emails and the authentication works just fine for everyone with such an email address ([email protected]). However, we have a few external partners (freelancers etc.) with gmail.com email addresses ([email protected]) which we would also like to authenticate using Google OAuth.

We would like to keep using $ngo_domain such that all our employees (present and future) will be automatically authenticated, in combination with $ngo_whitelist for those few special users who have gmail.com addresses but not within our domain, like this:

set $ngo_domain "example.com";
set $ngo_whitelist "[email protected] [email protected]";

However, it seems that this is by design currently not possible since $ngo_domain is not respected if either $ngo_whitelist or $ngo_blacklist are set.

lua_package_path no longer supported?

On V1.1.1, running into exception unknown directive "lua_package_path" in /etc/nginx/nginx.conf when creating nginx pod.
Specifying the absolute file path to lua in http block in nginx.conf works fine in V1.1 but not V1.1.1

http {
    lua_package_path "/etc/nginx/lua/?.lua;;";

Has there been any changes to loading the nginx-lua package?

Documentation lacking some non-obvious details

Unless you know Lua, using this project is more difficult to use than it should be.

It needs to be mentioned in the documentation that some of the variables, such as $ngo_domain, are non-optional and need to be set, at least to the empty string otherwise you get a traceback where lua complains about calling len() on a nil value.

More subtle is how to deal with lua ssl certificate verify error: (20: unable to get local issuer certificate), which is caused by use of resty.http and thus you need to provide:

lua_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;

Vulnerable to CSRF attack

The implementation uses the the redirect_url as state param. The returned state is never checked anyway. state should be a random nonce to prevent CSRF attacks.

whitelist some IPs

Hi

We need to whitelist some internal IPs ... how to achieve this?
i'm trying this, but does not seem to work:

allow 192.168.1.1/32;
satisfy all;

If there is no support for this, how about adding $ngo_whitelist_ip and $ngo_blacklist_ip , that will bypass oauth and always return 403 respectively

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.