Git Product home page Git Product logo

grant's Issues

Grant/Purest use with shopify

Hi, You might have noticed my tweet. I'm trying to use Grant and Purest for an app that I'm writing. The testing was going well. I was able to authenticate in test but once I deployed to Heroku, I ran into "Oauth error invalid_request: The redirect_uri is missing or not whitelisted" error. I mapped it in exactly the same way you did in the Grant demo app but am still getting this error.

As well, I'm not totally clear on how to use Purest. I have a clear idea of the REST api call for shopify but don't see what parameters I need to supply to and in what structure to use it for Shopify. Any and all help will be greatly appreciated.

Kind Regards,

John (legendsofthesport.co)

Path prefix

I have an api that I have mounted on an url like this:
http://localhost/v2/social/

My provider callbacks are configured like this:
/v2/social/connect/twitter/done

The callbacks in between however, do not know about the /v2/social part. These callbacks will come back to http://localhost/connect/twitter/callback.

I tried setting the grant.server.host to localhost/v2/social, but this didn't work. I dived a bit into the source code and added a quick fix to be able to specify path prefix options. I tried it with express and it seems to work.

Here is what I did: arjanfrans@b18df70

In the config I then have something like this:

{
  server: {
    protocol: 'http',
    host: 'localhost',
    transport: 'session',
    path: '/v2/social'
  },
  twitter: {
    key: 'keyhere',
    secret: 'secrethere'
    callback: '/connect/twitter/done'
  }
}

To me it seems like a missing feature. It would be nice to see it integrated. I could work on a proper pull request if I have some time next week.

Thoughts?

Thanks.

How to debug?

I am trying to get the amazon provider to work, I'm having problems but I have no idea what the problem is. I have Facebook working. How does one debug this? I'm thinking of writing a simple server using just the node https server (since it seems like Amazon only works with https, not http). Any help, ideas, suggestions? Thanks

Avoiding sessions

Is there a reason why Grant requires the use of sessions and cookies?

Can't use reserved words as custom parameters

Some providers have custom parameters that can't be provided because they are reserved. Typically, Trello has a name parameter that is used to show the application name on the authentication page. It would be nice to be able to use reserved words as custom parameters.

Order of app.use(mount(grant)) and app.use(router(app))

It seems if I do it in this order:

app.use(router(app));
app.use(mount(grant));

Then on my final callback, eg. /handle_oauth_response route, ctx.params is not populated.

If I do it in reversed order, eg.

app.use(mount(grant));
app.use(router(app));

Then things are fine, I can get /login/:provider as ctx.params.provider.

I haven't had the chance to look at it more closely, could be an incompatibility between koa-router v4 and v3 (I am using v4, grant-koa is on v3).

But I believe the final url is out of grant's scope right? Are they also reserved routes?

Github oauth: redirect_ui mismatch

Hey,

I'm using grant for github auth in my app and the redirect uri seems to equal myapp.com/connect/github/callback&state= instead of myapp.com/connect/github/callback.

Here's my config:

{
    server: {
      protocol: 'http'
    , host: 'http://myapp.org'
    }
  , github: {
      key: 'key'
    , secret: 'secret'
    , scope: []
    , callback: '/login_github_callback'
    }
  }

Problem with Trello

I'm trying to connect to Trello but it doesn't seem to be working.

Here's my config:

"trello": {
    "key": "***",
    "secret": "***",
    "scope": ["read"],
    "callback": "/authorise/trello",
    "custom_params": {"name":"test", "expiration":"never"}
},

Neither the Allow or Deny buttons are working. Looking at the console when clicking Allow it seems to do a POST call to https://trello.com/1/token/approve which is cancelled.

The post data contains: approve, requestKey, signature params.

Any idea what I've done wrong here?

Express empty req.session.grant

config:

{
    "server": {
        "protocol": "http",
        "host": "localhost:3000"
    },
    "intuit": {
        "request_url": "https://oauth.intuit.com/oauth/v1/get_request_token",
        "authorize_url": "https://appcenter.intuit.com/Connect/Begin",
        "access_url": "https://oauth.intuit.com/oauth/v1/get_access_token",
        "oauth": 1,
        "key": "mykey",
        "secret": "mysecret",
        "callback": "/api/connect/intuit",
        "custom_params": {
            "datasources": {
                "quickbooks": true,
                "payments" : false
            }
        }
    }
}

app.js

var Session = require('express-session')
    , session = Session({secret: 'very secret', resave: true, saveUninitialized: true})
    , Grant = require('grant-express')
    , grant = new Grant(config.connect);

app.use(session);
app.use(grant);

app.get('/api/connect/intuit', function (req, res) {
    console.log('----------------------------------------------------');
    console.log(req);
    console.log('----------------------------------------------------');
});

I'm getting {"error":"Grant: missing session or misconfigured provider"}.
When logging req.session in grant/lib/consumer/express.js:98 I get:

{ cookie: 
   { path: '/',
     _expires: null,
     originalMaxAge: null,
     httpOnly: true } }

So it seems like the grant details aren't being updated to the session.

I was using this example as reference: https://github.com/simov/grant/tree/master/example/express

What am I missing?

Change provider config dynamically within the backend

From what I understand, is that you can override provider configuration by posting a form to an override route.

I need to override the provider config, but this is not controlled from a form. I have a database that contains keys and secrets. I want to override provider configurations within my backend application, by doing a query to my database to get the key and secret from a provider.

I achieved this by putting in a callback that is called when a connect is done (not a very elegant solution though):
https://gist.github.com/arjanfrans/71c180e7b3274545e2a500b2f8db2233#file-express-js-L10
https://gist.github.com/arjanfrans/71c180e7b3274545e2a500b2f8db2233#file-express-js-L71

Inside this callback I do my database query and then override the config with my key and secret added.

I basically need the dynamic override ride, without the form part. Is what I am trying to achieve possible with the current version of Grant?

Set state by default?

Now that I think of it, we should just generated random state by default, right? This is the secure approach and grant is already using session (which it might as well use to store state).

Any reason for us to do a dynamic overwrite manually?

customized redirect URL ?

README says that:

For redirect URL of your OAuth application you should always use this format:

protocol://[host]/connect/[provider]/callback

But in my application I'd like to use a customized redirect URL, for example:

Is it possible ?

Oauth callback comes with empty object {}

I am a bit new to Hapi, and may be its just a config issue but I am not able to set up google auth in my service with grant.

Config

var grantconfig = {
  'server': {
    'protocol': 'http',
    'host': 'localhost:3333',
    'callback': '/callback',
    'transport': 'session',
    'state': true
  },
  'google': {
    'key': GOOGLE_CLIENT_ID,
    'secret': GOOGLE_CLIENT_SECRET,
    'scope': ['email', 'profile' ],
    'state': 'secret122',
    'callback': '/handle_google_callback'
  },
};

Server.js

var Hapi = require('hapi');
var Good = require('good');
var Path = require('path');
var yar = require('yar');
var grantconfig = require('./grantconfig');
var Grant = require('grant-hapi');
var grant = new Grant();
var server = new Hapi.Server();


server.connection({
  host: 'localhost',
  port: 3333
});


server.route({
  method: 'GET',
  path: '/handle_google_callback',
  handler: function (request, reply) {

    reply(JSON.stringify(request.query));
  }
});


server.register([
  {
    register: grant,
    options: grantconfig
  },
  {
    register: yar,
    options: {cookieOptions: {password: 'grant', isSecure: false}} 
  },
  {
    register: Good,
    options: {
      reporters: [{
        reporter: require('good-console'),
        events: {
          response: '*',
          log: '*'
        }
      }]
    }
  }  
], function(err){
  if(err){
    throw err; // something bad happened loading the plugin.
  }


  server.start(function(){
    console.log('Server running at:', server.info.uri);
  });
});

And This is response in the server.

150726/185255.451, [response], http://localhost:3333: get /connect/google/callback {"code":"5d09c0185472d78fa55a","state":"secret122"} 302 (1733ms) 
150726/185257.188, [response], http://localhost:3333: get /handle_google_callback {} 200 (2ms) 

As you can see, /handle_google_callback is not getting anything in response.
Can you please spot the issue?

using grant locally.

ha any one manged to get this to work locally?

I've tried the following with varying levels of failure:
make domain name /etc/hosts and use that, which all most worked (URL issues),
use "http:/localhost:8000/connect/github" sort ofworked with some but not offten.
use "http://127.0.0.1:8000/connect/github" much the same no auth.

Tried the examples... got much the same results.
would using a domain pointed to my local ip and network address work?

Does not work with hapi-auth-jwt2

If I add to my working code the npm module hapi-auth-jwt2, then all calls to /connect/provider returns 401.
How I added it:
as plugin in hapi require('hapi-auth-jwt2')
with a configuration

    server.auth.strategy('jwt', 'jwt', {
      key: config.JWT.SERIAL,
      validateFunc: jwt.validate,
      verifyOptions: {
        algorithms: [ config.JWT.ALGORITHM ],
        ignoreExpiration: true
      },
      headerKey: config.JWT.HEADER
    });

    server.auth.default('jwt');

Then the routes for grant have the configuration for disable jwt:

    config: {
      auth: false
    }

It returns the body
{"statusCode":401,"error":"Unauthorized","message":"Missing authentication"}
and the headers

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Token
content-type: application/json; charset=utf-8
cache-control: no-cache
vary: accept-encoding
content-encoding: gzip
Date: Wed, 26 Oct 2016 08:30:58 GMT
Connection: keep-alive
Transfer-Encoding: chunked

Do you have an idea why this is happening and could it be resolved by me or you?

Client Credential grant_type

Great awesome library! Does Grant-Express handle Client Credential grant type (without authorization code). If so, can I find how to set it up?

In config.json, I set it up:

  "custom": {
    "access_url": "https://myapitprovider/token",
    "oauth": 2,
    "key": "xxxxx",
    "secret": "xxxxxx",
    "callback": "/handle_callback"
  }

Thanks.

Changelog

Can we get a changelog put together for this package?

User/password authentication strategy / custom oAuth provider

I just started a new webapp-project using the application framework Strapi which offers authentication via grant. I want my own authentication in the webapp but am "forced" to use grant.

Is there an easy way to set up/fake my own oAuth provider, will I need to use middleware like koa-oauth-server or is there even a simple user/pw strategy for grant?

Food for thought

Not so much an issue: I just saw this config.json from microsoft azure cloud service video.

Here it is:

screenshot_2015-08-02_18-48-51

Due to the nature of where I found this video, I feel I must explain myself. I was re-evaluating freedom-clouddom, and was forgiving video as a medium of what should be encyclopedic information which is a tangent of actually looking for surveying software, modular, deployable. (know any?).

"response.raw" not containing extra parameters

Using this configuration:

var connectConfig = {
            "server": {
                "protocol": "http",
                "host": "localhost:3000",
                "transport": "session",
            },
            "intuit": {
                "request_url": "https://oauth.intuit.com/oauth/v1/get_request_token",
                "authorize_url": "https://appcenter.intuit.com/Connect/Begin",
                "access_url": "https://oauth.intuit.com/oauth/v1/get_access_token",
                "oauth": 1,
                "custom_parameters": ["realmId", "dataSource"],
                "key": "MY_KEY",
                "secret": "MY_SECRET",
                "callback": "/oauth/connect/intuit"
            }
        }
        ;
app.use(new Grant(connectConfig));

When I print console.log(req.session.grant.response.raw); I get:

{ oauth_token_secret: 'THE_TOKEN_SECRET',
  oauth_token: 'THE_TOKEN' }

Even though I do see a request to the default callback, with those params:
http://localhost:3000/connect/intuit/callback?oauth_token=THE_TOKEN&oauth_verifier=VERIFIER&realmId=A_CORRECT_REALM_ID&dataSource=QBO

I'm guessing I'm just doing something wrong here... Any help would be greatly appreciated :)

Use jwt instead of cookie?

I would like to use response data from each provider to create a json web token instead of the generated cookie.

How might I be able to do this using grant?

Koa2 implementation question

Any thoughts on how to handle Koa consumer after Koa2 released?

Chrome 55 released today and there is progress on a Node7 release that will ship with V8 5.5.
Node may land with official async/await support in less than a month.
Same for Koa2.

Would you ship a Koa2 version or replace existing one?
Thanks.

Specific information through the login process

Hi, thank you for this amazing project!
I have this requirement and I'm wondering if I can accomplish it with your library. Depending on some parameters, just after the login I have to assign a different role to the logged user.
This means that I'd want to specify "log the user with the provider and after assign him the role X".
I cannot instantiate two provider for the same type (and I don't need to), but how can I "inform" the callback that the login process has started in one way or another, that the role X has been requested?

Session Issues

I'm trying to use grant with koa, but my boss is pretty insistent that we not use session store for ANYTHING except the auth flows. So I can't just do what you examples do:

app.use(session(app)

I'm using the koa-joi-router and I tried this:

router.use('/connect', session(app))

to limit it to just the grant endpoints but I keep getting an error that I the session is missing or misconfigured. Based on some logs I added to grant in my node_modules folder, it looks like the redirect comes back and grant can't find session.grant or a provider. I added this to the callback generator around line 119:

    var grant = this.session.grant || {}
    var provider = config.provider(app.config, grant)
    var flow = flows[provider.oauth]
    console.log('callback invoked', this.session, provider);

which spits out: callback invoked {} {}.

Any suggestions or workarounds?

Problems with Yahoo

I'm using Node and Express 3 and tried the following:

var Grant = require('grant-express')
  , grant = new Grant({
"server": { 
  "protocol" : "http",
  "host" : "[my host]:[my port]"
},
"yahoo": {
  "key": "[OAUTH CONSUMER KEY]",
  "secret" : "[OAUTH SECRET]",
  "callback" : "[host and path for callback]"
}
});

var app = express()
app.use(express.logger())
app.use(express.cookieParser())
app.use(express.cookieSession({secret:'very secret'}))
// mount grant
app.use(grant)

When I hit the [host]/connect/yahoo URL, I get the following:

{
  "error": {
    "oauth_problem": "consumer_key_unknown"
  }
}

When I try the same consumer key and secret on the OAuth Playground app on your Heroku instance, with the Yahoo provider, it works fine.

Alternative on transporting access token

Currently the design is to do another redirect with GET, where querystring contains the access token. 2 things of this approach concerns me:

  1. For oauth 1, this can potentially be abused as we expose a GET route that have side-effects. Unlike oauth 2, access token does not expire, which makes it plausible through CSRF.
  2. This approach means extra security consideration are needed, for example web logs now contains user access token and secret, and logs can be less protected than database.

/connect/:provider/callback route is safe because the GET route is called with a randomized state.

In my previous projects I usually do that through sessions, it means:

  1. Access token and secret are not exposed, even to users.
  2. HTTPS + Signed cookies makes it a lot harder (if not impossible) to craft an attack on the final callback.
  3. Session cookies allow us to do things like oauth timeout (make sure oauth is started and completed within a reason timeframe)

Just something to consider.

Session managment with Hapijs

I'm using Grant with Facebook and Twitter and it works great but the problem is when I try to manage the session. The plugin generates a session called grant when it redirects to the facebook pr twitter login, and not when you logged in successfully. Do I need to use a custom session using yar ? Btw, awesome work ! Thanks.

Problem when using multiple scopes in facebook

Hi,

I am using grant express and trying to make the facebook example work. The twitter and e.g. moves examples work fine. With facebook there is a problem though. After testing it occurs only when there are multiple scopes defined in the config.

{
  "server": {
    "protocol": "http",
    "host": "localhost:3000"
  },
  "facebook": {
    "key": "(key)",
    "secret": "(secret)",
    "callback": "/handle_facebook_callback",
    "scope": [
      "public_profile",      
      "user_groups",
      "user_likes"
    ]
  }
}

When running, facebook fails to properly redirect back and the query string in the debug shows the scope parameters as url encoded comma separated list:

...&scope=public_profile%2Cuser_groups%2Cuser_likes

With a facebook error message. "Sorry, something went wrong."

When I replace (manually) in the query string with

...&scope=public_profile&scope=user_groups&scope=user_likes

it works fine. Regular commas also don't work manually in the querystring.

What am I doing wrong? I am inclined to think I am missing something totally obvious...

Thanks!

koa-grant: How to get the user info from session

I want to find a way to get the user info from the session after login.

For example:

// suppose user have login
router.get('/demo', function *(next) {
  if (!this.session.grant) {
    this.redirect('/login');
  } else {
   // get the user info from DB which I saved after user login successfully,
   //    eg, saved  in `/hanlde_provider_callback
    user = yield getUserInfo(this.session.grant.userId);
  }
}

Seems I can't add a new property to session in the /handle_provider_callback, so I don't how to connect the special session with the db item (I want to store users in my db). Do you have any idea?

Thank you.

CORS issue with Facebook/Twitter in Backbone request

Hello. I configured grant on my hapijs server and it's working great but when I try to make a request to myappp:9000/connect/facebook (as well with twitter endpoint) using a Backbone model i'm getting a CORS error message and model's promise is failing. I've enabled CORS on my hapijs server and I'm pretty sure this is not an issue with grant but maybe you could help me to solve it without change everything on my backend. I've running my frontend in myapp.com:3000 and my backend in myapp.com:9000 and basically what I need is to perform a request from my Backbone frontend and get a redirection to facebook/twitter login screen, after the user log in, the model should be filled with the information i'm returning from my custom endpoint (which is the one that connects to facebook/twitter). Thanks a lot !

Google authentication issue

I am using Grant for OAuth2 authentication with google in my express app. I supplied all the parameters in the config.json :

  {
    "server": {
    "protocol": "https",
    "host": "thooslo-com-shaunakde.c9.io"
  },
  "google":{
    "authorize_url": "https://accounts.google.com/o/oauth2/auth",
    "access_url": "https://accounts.google.com/o/oauth2/token",
    "oauth": 2,
    "custom_parameters": ["access_type"],
    "scope_delimiter":" ",
    "scope":["https://www.googleapis.com/auth/youtube","https://www.googleapis.com/auth/drive"],
    "client_id":"39109025743-veaeooi4v9ooirabeseujn8u2ohjbqf7.apps.googleusercontent.com",
    "client_secret":"DO8ozwoFqtP654jzi-wPQF10",
    "callback": "/users"
  }
  }

But it still refuses to send all the parameters. I get a "client_id" not sent error:

Error: invalid_request

Missing required parameter: client_id

Learn more
Request Details

    scope=https://www.googleapis.com/auth/youtube https://www.googleapis.com/auth/drive
    response_type=code
    redirect_uri=https://thooslo-com-shaunakde.c9.io/connect/google/callback

I modified the library to print out the URL and this is indeed the case:

Starting child process with 'node ./bin/www'
https://accounts.google.com/o/oauth2/auth?response_type=code&redirect_uri=https%3A%2F%2Fthooslo-com-shaunakde.c9.io%2Fconnect%2Fgoogle%2Fcallback&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fyoutube%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive
GET /connect/google 302 26.492 ms - 574

I did manage to get the first step to work once, by some permutation, but then it failed on client_secret not found.

Is there something I am doing wrong? The library seems to be non buggy.

Using Grant without sessions

For our APP, we arent using any Sessions but instead, using JWT tokens. Is there a way to use Grant without having to use sessions?

GitHub Oauth: redirect_uri_mismatch

I've been having some issues getting GitHub oauth working with Grant. I already have BitBucket Oauth set up correctly, so I'm using the library correctly. My setup:

GitHub
My callback URL in GitHub is set to http://local-kernl.com/api/v1/oauth/github/

local-kernl.com is an entry in my /etc/hosts file that directs the user to a virtual machine (vagrant) with Nginx installed. Nginx then acts as a reverse proxy to localhost:3000 where my Node app lives.

Node
My Node config looks like:

grant = new Grant({
  "server": {
    "protocol": "http",
    "host": "local-kernl.com",
    "transport": "session",
    "state": true
  },
  "bitbucket": {
    "__provider": {
        "oauth2": true
    },
    "key": "bitbucket-key",
    "secret": "bitbucket-secret",
    "callback": "/api/v1/oauth/bitbucket/",
  },
  "github": {
    "key": "github-key",
    "secret": "github-secret",
    "callback": "/api/v1/oauth/github/",
    "scope": [
      "public_repo",
      "repo",
      "repo_deployment",
      "repo:status"
    ]
  },
});

For BitBucket, when I hit /connect/bitbucket/ it makes the requests as expected and authorizes my app. With GitHub(/connect/github/) though, I get the response:

error=redirect_uri_mismatch
error_description=The redirect_uri MUST match the registered callback URL for this application.

The error is pretty obvious, but it doesn't look like I have the urls mismatched. I've tried several different iterations of this, including having it go straight to localhost:3000 with no luck. I read through your blog post @ https://scotch.io/tutorials/implement-oauth-into-your-express-koa-or-hapi-applications-using-grant, which I seem to be following correctly

Any help or ideas would be greatly appreciated. Thanks!

Invalid cookie value with Hapi

I get this error from grant when using with hapi:
{"statusCode":400,"error":"Bad Request","message":"Invalid cookie value"}

So I tried the examples in the git repo. The express example works as expected, and I can log into facebook with no issues.
The hapi example gets the same error (above) as my code. I'm using a copy of the same config file with both examples.
I'm using this url to test both: http://localhost:3000/connect/facebook

I've tried logging out of facebook and clearing my browser cache, but it didn't help.

Bump koa dependencies

Is there a repo for grant-koa, the meta package locked koa-session to 3.0.0 and it throws errors when used with 3.1.0

Having meta package also lead to a few problems: we can't contribute easily or debug package (ie. temporally fork the repo and set package.json to use forked/grant)

Allow custom providers

Any plans on allowing custom providers?

In config.init you could merge the passed in options into the result.

New koa consumer module fail to load

I think you're doing the right thing to split the consumers out of the main module but because I don't have express in my app it fails to load express when I require grant-koa because all consumers are required in index.js.

Perhaps they should be split into separate repositories with separate tests, so they're tested more like they will be used instead?

Supply additional headers with request

Hi there,

First, big props for this lib! Very easy to setup ๐Ÿ‘ I am trying to setup a connection with the discogs oauth api, but they require to send an additional user-agent header. How can I configure this in the grant lib? Here's my setup:

var express = require('express')
var logger = require('morgan')
var session = require('express-session')
var Grant = require('grant-express')

var grant = new Grant({
  'server': {
    protocol: 'http',
    transport: 'session',
    state: true,
  },
  'discogs': {
    key: process.env.DISCOGS_CONSUMER_KEY,
    secret: process.env.DISCOGS_CONSUMER_KEY,
    callback: '/handle_discogs_callback',
  },
})

var app = express()
app.use(logger('dev'))
app.use(session({ name: 'grant', secret: 'very secret', saveUninitialized: false, resave: false }))
app.use(grant)

app.get('/handle_discogs_callback', (req, res) => {
  res.end(JSON.stringify(req.query))
});

app.listen(4000, () => {
  console.log('Express server listening on port ' + 4000)
});

adding concur provider

A colleague created a fork in order to add concur as a provider to grant.

master...szafarcin:master

Wondering if there is a best practice on how to add new providers to grant? Can you assess the above fork on how practical it would be to merge it back into grant?

Thanks

grant-express app.use(session({secret:'grant'})) question

Hi, this is not really an issue just a question I couldn't find answer to anywhere.
What is the secret: 'grant' part stands for? I see it in different forms everywhere like secret: 'very secret'
am I supposed to replace 'grant'/'very secret' with a different value? what is it used for in the session function?

Thank you very much!

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.