colinskow / superlogin Goto Github PK
View Code? Open in Web Editor NEWPowerful authentication for APIs and single page apps using the CouchDB ecosystem which supports a variety of providers.
License: MIT License
Powerful authentication for APIs and single page apps using the CouchDB ecosystem which supports a variety of providers.
License: MIT License
Imagine that you create an App, and you want to only allow some users (Premium account) to access to the server to sync (like evernote).
Actually, you have to do it manualy, by calling superlogin.addUserDB and superlogin.removeUserDB by http requests (on another protocol).
It would be nice to only manage it like this :
The userDoc has an variable (array) of roles / access.
And in the config file, or when you make an addUserDB, you can specify the required access.
userDoc.access = ["basic", "premium"]; //The user's accesses
databases = {
"basicDatabase" : {
"requireUserAccess" : [] //Everybody can access it !
},
"premiumDatabase":{
"requireUserAccess" : ["premium"] //Only users with "premium" access can access it.
}
}
Then, you only have to manage the user's accesses, and when the user log-in, the session.userDBs is refreshed and allow the front part to easily identify the allowed ones.
For me, it should use a special access variable, because userDoc.role and userDoc.permissions are used for technical access to Cloudant and CouchDB ['_reader', '_writer', '_replicator'].
But I'm still questionning about the userDoc.role, for me it would be nice to use it, but what I saw in lib/dbauth/cloudant.js , it's transformed to permissions.
Use a filter on the userDBs sent into the session. I've a sample of that solution in this commit.
At the start of the user's session creation, parse userDoc.personalDBs and compare it to the access difined in the config (and don't forget the ones addes by addUserDB).
While parsing and comparing :
Do the user has access to that database ?
YES = Do you already have that database in userDoc.personalDBs ?
YES = No action
NO = superlogin.addUserDB(user, databaseName, ...)
NO = Do you have that database in userDoc.personalDBs ?
YES = superlogin.removeUserDB(user, databaseName, ...)
NO = No action
And even, to optimize and not always do a parsing, you can use a system of databaseSignature.
A) A superlogin config has a signature (like "abcdefg45")
B) A userDoc has a personalDBs signature
B) Before doing the parsing & comparing, the management system check the user's signature and config signature, and if it's the sames : the system know it doesn't need to re-do the checking.
I've tried something similar in this commit
For me the best, it to do it on the user's session creation, so it's not a big bottlenek, it won't surcharge the server, parsing all the users, and the front can get the good session.userDBs.
I think this feature might be really useful, and make superlogin more easy to use.
Hi. superlogin is very nice!! I love it. Best solution i found so far! :-)
I found one issue when you use a local couchdb-instance on the server.
In lib/user.js at createSession you build the url for the UserDBs with the dbServer.host, which is for example localhost:5984. But on the client, for example with IonicFramework, you can't connect to http://tokenxyz:passwordxyz@localhost:5984/dbname. Instead of localhost there must be the IP of my server. This should be configurable in the config on the server.
Following the quick start in the README, I can't register a new user:
# The node app is already up and running
$ curl -vX POST http://localhost:3000/auth/register \
-H 'Content-Type:application/x-www-form-urlencoded' \
-d '{"name": "Joe Smith", "username": "joesmith", "email": "[email protected]", "password": "bigsecret", "confirmPassword": "bigsecret"}'
* Trying ::1...
* Connected to localhost (::1) port 3000 (#0)
> POST /auth/register HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/7.43.0
> Accept: */*
> Content-Type:application/x-www-form-urlencoded
> Content-Length: 135
>
* upload completely sent off: 135 out of 135 bytes
< HTTP/1.1 400 Bad Request
< X-Powered-By: Express
< Content-Type: application/json; charset=utf-8
< Content-Length: 228
< ETag: W/"e4-KPd++izl8BaMF99qid8Zxw"
< Date: Tue, 29 Sep 2015 11:42:06 GMT
< Connection: keep-alive
<
* Connection #0 to host localhost left intact
{
"error": "Validation failed",
"validationErrors": {
"email": [
"Email can't be blank"
],
"username": [
"Username can't be blank"
],
"password": [
"Password can't be blank"
],
"confirmPassword": [
"Confirm password can't be blank"
]
},
"status": 400
}
Is there an extra setup step that I'm missing?
Could you please specify a engine constraint in your package.json
Example:
"engine": "node >= 0.4.1"
Hey people,
There is possible use the superlogin with the one db per user strategy?
cheers
Hi! This project sounds really interesting, and I wanted to give it a try.
But when I copied the code from the example, I got this error:
Memory Adapter loaded
/var/www/pouch-superlogin/node_modules/superlogin/lib/util.js:99
Object.keys(providers).forEach(function(provider) {
^
TypeError: Object.keys called on non-object
at Function.keys (native)
at Object.exports.addProvidersToDesignDoc (/var/www/pouch-superlogin/node_modules/superlogin/lib/util.js:99:10)
at new module.exports (/var/www/pouch-superlogin/node_modules/superlogin/lib/index.js:51:21)
at Object.<anonymous> (/var/www/pouch-superlogin/index.js:39:18)
at Module._compile (module.js:460:26)
at Object.Module._extensions..js (module.js:478:10)
at Module.load (module.js:355:32)
at Function.Module._load (module.js:310:12)
at Function.Module.runMain (module.js:501:10)
at startup (node.js:129:16)
at node.js:814:3
I tried adding providers: { local: true }
to the config
object, and that seemed to work, but the next step (posting to /auth/register) is failing:
{ error: 'Validation failed',
validationErrors: [TypeError: Cannot read property 'match' of undefined],
status: 400 }
So maybe adding local: true
was not a good idea =)
Could you provide directions on how to get the example code running?
So, during app life-cycle, design docs may be added/edited against private and shared DBs. Is there a mechanism in SL right now which can automatically update them into the existing DBs during app startup?
Why this is important?
The DBs are created and maintained by SL. It needs the design docs in a certain format (seed) and already has support to add them in the created DBs. However as the app develops further, there will be changes to these docs and it'd be tedious to have an update mechanism outside of SL.
As of now, my test app is in early stages of development, so it is trivial for me to reset the SL DBs and begin again, in case I need new design docs seeded, but this is not scale-able and I'd soon need to maintain new/changed design docs in both the CouchDB format as well as seed format and would need to come up with a process of updating these changes.
Routes like /login
are already protected, but request spamming could compromise security in other ways such as with password reset or run up a high bill with the CouchDB provider. I would like to implement rate limiting middleware such as express-brute in order to mitigate such risks.
Normally a single couch database contains a mixture of document types but sadly this appears to be rather difficult to do with the current superlogin
structure. I'm basing this on a quick look at auth views like this one ...
username: function(doc) {
emit(doc._id, null);
},
Please correct me Colin if I missed something here but if I'm right can we can perhaps find some workarounds for now and maybe include some refactoring to support mixed doc types in the user database in the future roadmap? Of course you may have had some other reasons I'm not aware of so please share them. I've seen one fork of this project that I think got the complete wrong message about CouchDb and went off to create a database per record type. I think they just blindly followed your pattern.
Apologies for my next Couch 101 rant ... I know it's all rather basic stuff but I'm unsure if these reasons were missed.
Why & How to mix doc types in one database ...
Maintaining a mixture of records in the one db instance also has many benefits including: Simpler/safer db syncing, using single updates via _bulk_docs and single gets via _all_docs, being able to watch all change with a single change feed and leverage many map/reduce view patterns that need to emit a mixture of record types (and often efficiently reduce and cache/index the result). In my current app I want to do this with the user docs but can't since they are in a different sluser database where _id values might collide unless prefixed (but this would break the current views that assume username == doc._id
).
A common convention to distinguish document types is to place a prefix on _id values and/or to add a type
attribute. The _id prefix means you can then use startkey
and endkey
in either a view or _all_docs queries to perform efficient indexed lookups for the subset of record of the given type.
I've just run some very basic testing of your demo app with the following:
I want to add several user DBs when a particular type of user signs up. I can't do this with defaultDBs as I don't want create these DBs for every user account, only ones with a particular type. Here's my code:
var dbs = [
'example',
'dbs',
'to',
'add'
];
superlogin.on('signup', function(userDoc, provider) {
if (userDoc.user_type === 'special') {
dbs.forEach(function(db) {
superlogin.addUserDB(userDoc._id, db).then(function(res) {
console.log('Success: ' + res);
}, function(err) {
console.log('Error: ' + err);
});
});
}
});
dbs is an array of the DB names that I want to create for a user with the user_type "special". The first DB gets created but the others all throw "Document update conflict" errors. I'm assuming that I'm using addUserDB incorrectly, but am stumped as to how. Any help would be appreciated! I've tried other approaches as well but nothing seems to work.
Right now in the code there are still some console
here and there.
I would suggest to use debug or debug-logger instead.
This would allow the developers to decide and they wanna print or not the logs.
The current property name is confusing because it comes out as giving access to the privateDBs of a user. It also conflicts with the other APIs like addUserDB
and removeUserDB
.
I recently upgraded to NodeJS 6.1.0, npm 3.8.6. Now Superlogin APIs result in this deprecation warning -
(node:11324) DeprecationWarning: crypto.pbkdf2 without specifying a digest is deprecated. Please specify a digest
When a user register, would be nice to be able to populate the user databases with default values.
Something we could put under model perhaps.
I tried out including some info in a profile
property but no luck. I also looked through the superlogin code a little and couldn't find a place where this is supported. Is it not?
First off, this really looks like a great solution and the first library I have seen to support all thèse features. Nice job!
Do you have an idea how this could work with pouchdb in the browser? Maybe as a better replacement to pouchdb-authentication?
Would be nice to be able to configure a different session adapter other than Redis or memory.
We are not sure we gonna use Redis for our project, so now we are stuck with using the memory adapter but like you can imagine this is far from being ideal.
Some unit tests depend on state from prior tests or on test case order. This is an anti-pattern for unit testing.
This means that you can't selectively run unit test cases (using it.only("...") { ... }
).
Example: user.spec.js
... it("should prepare the database", ...)
and it("should destroy all the test databases" ...)
create and destroy databases. I think they should be converted to before()
and after()
or perhaps beforeEach()
or afterEach()
.
Please add a getUserDB(user_id, dbName, type)
method which returns a PouchDB instance with admin access. This will allow the server API to easily manipulate the user DB with admin privileges. Without this API, the only way is to share the CouchDB admin credentials separately with the rest of the server code.
This will also go in parallel with other PouchDB instances provided by superlogin, like usersDB
and couchAuthDB
.
At launch, the dbServer.userDB doesn't have any security.
dbServer.user should be the only account to access it.
Hello,
I need to manage the password reset from that and not from a website...
I launch the command :
superlogin.forgotPassword("[email protected]");
And I receive a proper email :
When I click on the link :
I got this error : Cannot GET /password-reset/mK9671GeQKWOo74U4Y6MIw
and from the Heroku Logs :
2016-04-24T09:18:20.356789+00:00 heroku[router]: at=info method=GET path="/password-reset/mK9671GeQKWOo74U4Y6MIw" host=*******-superlogin.herokuapp.com request_id=8a4be95f
-699a-4abc-af19-41da2f0022a1 fwd="78.***.***.64" dyno=web.1 connect=0ms service=2ms status=404 bytes=427
I can see on the Superlogin-demo the same e-mail with this link :
https://u19**480.ct.sendgrid.net/wf/click?upn=4RVtDsLk14tpYYyLSbT246p1i9JuyL69W3GiHHUdnVKuej6uxOSagz5XJ-2Ff7-2FHnwif3PaOVAz6M1xcMFA9-2Fnhl5hPrHL616COgWtIw_BQvb1-2B8lEcvg1yJWswS37EQzdeiO5rjciTkF5tMolWwvOW0rYeC9leKCRGNN7toZFCHQvphEzfXwnqq6QZFIRLku0kYTN-2F7pfIZqFk1HoRhzLnGgcoeZPPMEGIidjl-2FA5B2rJUZQzXcjHUfgcILtMYrtiGU6ROx6rYrjDjc-2Bg-2B-2FPHSNN6ssq7xDB1C4tejBm-2F12cUPN6f-2F-2FpnnsHk5P13Q-3D-3D
This link redirect to the superlogin-demo
What Can I do in my context ?
[Mobile hybrid App] => [Email with Reset Link] => [form hybrid App] is not possible.
Is this workflow a good one ?
Like something with a code pin :
The config of the database url on the server (local ip) might differ from the url the client need to access from (public ip).
protocol, host and port could be completely different.
Hello all,
I've been working a little with this awesome plugin and I found an issue with nodemailer, when I try to register a new user with sendConfirmEmail: true I receive:
{ cause: [Error: Unsupported configuration, downgrade Nodemailer to v0.7.1 to use it],
isOperational: true }
I managed to solve it by doing so... But I still have the feeling that I'm missing something since by default Superlogin installs [email protected]. Could you guys give me some clues about it?
Thanks a lot!
Hello,
Here is the example on superlogin / lib / index.js
//Working
emitter.on('testemit1', function (){
console.log('1 - ring ring ring');
});
var mailer = new Mailer(config);
var user = new User(config, userDB, couchAuthDB, mailer, emitter);
emitter.emit('testemit1');
emitter.emit('testemit2');
var oauth = new Oauth(router, passport, user, config);
//Not Working
emitter.on('testemit2', function (){
console.log('2 - ring ring ring');
});
Hi,
From the current documentation in the readme, and looking into the code, I think the privateDBs are not restricted to just the user_ids they are based on. E.g. If I am logged in as A so long as I know B's user_id (and private DB URL), I can read/write to it.
Considering the above assumption is right, how can I add a design doc with validate_doc_update
function to disallow users whose id won't match with the DB name? If I can do that, then even if the userID is leaked, a different user won't be able to read/write into this user's DB even after guessing the DBURL. Although CouchDB is new to me, I don't think I can get DB Name within the validate_doc_update
function. As an alternative, is there a way in which I can match the logged in user's ID with the DBUrl on the server (although I take it that once the client has the DB URL, all requests would directly hit the remote CouchDB, not go through the express app)?
My intention is to prevent any user other than the privateDB user from having any access to it.
Thanks in advance!
Although the docs hint towards this, I wasn't sure. The reason why I ask is because I am leaning towards the PouchDB-find project for easier in-browser queries compared to the PouchDB views. It sadly only works (for remote instances) with PouchDB server or CouchDB 2.0 preview. I have decided to use PouchDB server and hence this question.
Thanks in advance!
Sukant
I would like to be able to choose myself what is mandatory for the password for my users.
Being stuck to "minimum 6 characters" is not ideal for the application I'm trying to build.
If it already exist please point me to the right direction.
Hi,
If I try to register a user on local secured couchdb instance with "userDBs" option. It fails with
[SyntaxError: Unexpected token u]
[SyntaxError: Unexpected token u]
SyntaxError: Unexpected token u
at Object.parse (native)
Here is my sample config
var config = {
dbServer: {
cloudant: false,
protocol: 'http://',
host: 'localhost:5984',
user: 'test1',
password: 'test1234',
userDB: 'sl-users',
couchAuthDB: '_users'
},
userDBs: {
defaultDBs: {
private: ['test']
}
}
}
I tried to debug and the problem is with the way it tries to send request to create the user on localDB which in turn is because of the way superagent.js client parses the url.
I was able to get this working for local env by changing the "getDBURL()" in util.js to not add username and password to the url instead use
.auth('couchdb_admin_username', 'couchdb_admin_password')
in "createDB()"
I hope this helps in fixing the problem
Thanks,
Neeraj
This check improved security.
I've some problems using superlogin while creating an app with nodewebkit
I've used superlogin-demo as a base.
But had to overwrite some parts like the logout in routers and the authorization on the profile refresh.
The main problem is that in superlogin, passeport's "bearer" and header.authorization on http request are often used to find the user's session. But it looks like if the server and the client aren't hosted on the same computer, it doesn't work.
Here are some "patch" that I used in my fork :
<base href="/">
" in index.html, and remove the use of $location in src/app.js and src/token/token.js (By passing the problem parts as comment){ error: 'unauthorized', status: 401 }
POST /auth/logout 401
If you try it in localhost on you computer, you should need to add cors to the nodejs server
I'll try to easily upload a .zip with the nodejs server and apache client ready, so it'll be more easy to setup.
I've also tried it with a distant server, to see if it's not the cors and localhost the problem but the same problem occurs.
So, is there a way to solve more easily my problems than my patches ?
Hi,
Currently the util.getDBURL API injects username and password into the private DB URLs. Is there a way to use session tokens for authorization instead of this? I understand that in Production, the Superlogin server will be behind SSL, but if there's an alternative, it'd be great.
Thanks!
Not entirely sure if this is a FE or BE issue but (considering the FE library) when logging out it might trigger the checkRefresh call which in fact will update the data in sl-users
after this one has been read for the logout code.
Which will trigger a conflict error when logout tries to write the data back in sl-users.
When reloading my SPA I need to know for sure that my current session (in local storage) is still valid.
I'm going through refresh
for now, but ideally I would like to avoid writing in the database just for that.
But the idea would be really similar and could even trigger a login
event on the frontend.
Hello,
I added another shared DB after some users registration.
The access to the new Shared DB are generated with new users created after its creation.
But for previous Users, the access to this new Shared DB is not generated then they can't have access to it.
Is there a way to connect the previous created users to this new Shared DB ?
t0 : I config a A-sharedDB.
t1 : Some users registered with an access to the A-sharedDB.
t2 : I config a B-sharedDB.
t3 : New users registrered with an acccess to the A-sharedDB and B-sharedDB.
but when t1 users deconnect and reconnect to superLogin they only get an access to the A-sharedDB and not to the B-sharedDB.
Ron
setting local.loginOnRegistration: true, does not return a session.
Tested it on your sample app where the issue also exist.
Looks like you are looking for security.loginOnRegistration, however per the docs it should local.loginOnRegistration
Hi all!
I'm trying to change 'defaultRoles' at runtime usign superlogin.config.setItem as indicated on Main API documentation, I printed out the config after change it so I'm sure setItem works ok ,but when I'm registering new users it gets the value previously defined on my config file.
exports.postUser = function(req, res, next){
superlogin.config.setItem('security.defaultRoles', [ 'user', 'whatever' ])
superlogin.createUser(req.body, req)
.then(function(){
res.status(201).json({ok: true, success: 'User created'})
}, function(err){
res.json(err)
})
}
Any help will be appreciated
Hello,
I have 2 questions concerning Redis
Why using reddit instead of a levelDown PouchDB ?
Is it possible to update the DOC in order to explain how to install Reddit and how to configure it for Superlogin ?
regards,
Ron
When extending the user model, nested arrays are not concatenated their positions are overwritten.
ie:
var userModelConfig = {whitelist: ['1','2','3','4','5','6']}
var userModel = {whitelist: ['a','b','c']}
$.extend(true, {}, userModel, userModelConfig)
//returns
{"whitelist":["1","2","3","4","5","6"]}
and
$.extend(true, {}, userModelConfig, userModel)
"{"whitelist":["a","b","c","4","5","6"]}"
As you can see the index position of the array is overwritten rather then extend or merged.
A potential solution would be to transform the arrays into objects, extend them and transform them back.
I was going to try to fix it and submit a pull request, but i dont have the time tonight so i wanted to get this to you, maybe you have a quicker solution.
Looks like in line 884 of user.js you have
return mailer.sendEmail('forgotPassword', user.email, {user: user, req: req, token: user.forgotPassword.token});
However if you havent confirm your email yet, then user.email does not exist.
I tried to connect my project with Cloudant with this code
dbServer: {
protocol: "https://",
host: "cloudant_account.cloudant.com/_all_dbs",
user: "cloudant_account",
password: CLOUDANT_ACCOUNT_PASSWORD,
cloudant: true,
userDB: "sl-users",
couchAuthDB: "_users"
}
But I think it doesn't work as the APIs don't work properly. Is there anyway for me to check if CouchDB is connected? Do you think it will be better if we can add a log for it? Also, if I made something wrong with my configurations, please help me!
Hi there. I'm following the Quick Start example in the README but getting the following message. Do you know what the issue could be? I'm running node v4.2.2.
Correction: I just tried the vanilla Quick Start example server code and it worked 👍 . My bad for trying to integrate this first into an existing express server. Now it's time to figure out why I get this message with when using the integrated example code.
Does superlogin have support for anonymous users?
I'm testing superlogin module and I'm able to create new users into the dbServer.userDB database and login with them.
Now I would like to login with an user from dbServer.couchAuthDB, without registering that user with superlogin with no success.
Is it supported to login with users from _users ?
If user was signed up by Facebook etc. and doesn't have password and we try to sign in entering his username and any password an application (including SuperLogin Demo) hangs.
Hi,
Is there any plan to support additional DBs?
Thxs
havent had a chance to look into it but here is the error
Unhandled rejection TypeError: user.createSession is not a function
node-2 | 2016-01-08T05:08:20.783460263Z at /usr/src/app/node_modules/superlogin/lib/routes.js:114:25
node-2 | 2016-01-08T05:08:20.783483814Z at processImmediate [as _immediateCallback] (timers.js:383:17)
node-2 | 2016-01-08T05:08:20.783502865Z From previous event:
node-2 | 2016-01-08T05:08:20.783520506Z at /usr/src/app/node_modules/superlogin/lib/routes.js:112:10
node-2 | 2016-01-08T05:08:20.783539709Z at Layer.handle [as handle_request] (/usr/src/app/node_modules/express/lib/router/layer.js:95:5)
node-2 | 2016-01-08T05:08:20.783556118Z at next (/usr/src/app/node_modules/express/lib/router/route.js:131:13)
node-2 | 2016-01-08T05:08:20.783571577Z at Route.dispatch (/usr/src/app/node_modules/express/lib/router/route.js:112:3)
node-2 | 2016-01-08T05:08:20.783586956Z at Layer.handle [as handle_request] (/usr/src/app/node_modules/express/lib/router/layer.js:95:5)
node-2 | 2016-01-08T05:08:20.783602485Z at /usr/src/app/node_modules/express/lib/router/index.js:277:22
node-2 | 2016-01-08T05:08:20.783617911Z at Function.process_params (/usr/src/app/node_modules/express/lib/router/index.js:330:12)
node-2 | 2016-01-08T05:08:20.783633408Z at next (/usr/src/app/node_modules/express/lib/router/index.js:271:10)
node-2 | 2016-01-08T05:08:20.783648799Z at Function.handle (/usr/src/app/node_modules/express/lib/router/index.js:176:3)
node-2 | 2016-01-08T05:08:20.783664379Z at router (/usr/src/app/node_modules/express/lib/router/index.js:46:12)
node-2 | 2016-01-08T05:08:20.783679645Z at Layer.handle [as handle_request] (/usr/src/app/node_modules/express/lib/router/layer.js:95:5)
node-2 | 2016-01-08T05:08:20.783695292Z at trim_prefix (/usr/src/app/node_modules/express/lib/router/index.js:312:13)
node-2 | 2016-01-08T05:08:20.783710675Z at /usr/src/app/node_modules/express/lib/router/index.js:280:7
node-2 | 2016-01-08T05:08:20.783726098Z at Function.process_params (/usr/src/app/node_modules/express/lib/router/index.js:330:12)
node-2 | 2016-01-08T05:08:20.783741798Z at next (/usr/src/app/node_modules/express/lib/router/index.js:271:10)
node-2 | 2016-01-08T05:08:20.783757351Z at logger (/usr/src/app/node_modules/morgan/index.js:136:5)
node-2 | 2016-01-08T05:08:20.783773602Z at Layer.handle [as handle_request] (/usr/src/app/node_modules/express/lib/router/layer.js:95:5)
node-2 | 2016-01-08T05:08:20.783794548Z at trim_prefix (/usr/src/app/node_modules/express/lib/router/index.js:312:13)
node-2 | 2016-01-08T05:08:20.783832303Z at /usr/src/app/node_modules/express/lib/router/index.js:280:7
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.