GithubHelp home page GithubHelp logo

ddspringle / framework-one-secure-auth Goto Github PK

View Code? Open in Web Editor NEW
31.0 7.0 5.0 590 KB

An example fw/1 application with secure single and two-factor (2FA) authentication and session management functions

License: Apache License 2.0

ColdFusion 99.41% HTML 0.29% TSQL 0.31%

framework-one-secure-auth's Introduction

FW/1 Secure Authentication Example

This project is an example fw/1 application with secure single and two-factor (2FA) authentication and session management functions as follows:

  • Based on basic example fw/1 application
  • Uses subsystems for separation of concerns, securing only the admin subsystem
  • Includes a SecurityService component that has encryption, decryption, hashing, password generation and session management code
  • Includes a security controller for managing session and request scope session management within the admin subsystem
  • Uses cookies and object cache for session management
  • Includes HMAC protection for session cookies to help prevent tampering
  • Rotates the session id on each request and utilizes form tokenization to help prevent CSRF
  • Federates the login with a cookie and referrer requirement
  • Protects the users password from disclosure with SHA-384 hashing during login
  • Stores user data in encrypted format in the database
  • Default CBC/PKCS5Padding defined for encryption algorithms
  • Includes HTTP security headers designed to reduce attack surface
  • Uses keyring stored on disk to load encryption keys instead of hard-coded in the Application.cfc
  • Includes functions for reading, writing and generating a random keyring file
  • Includes functions for checking for, adding, removing and importing blocked IP's
  • Includes functions for checking for, adding, removing and importing watched IP's
  • Includes functions for managing watched/blocked IPs by catching common parameter tampering/sql injection attacks
  • Includes optional addDate true/false parameter to uberHash function to append the current date to the input value on hash
  • Includes 'dummy' cookies for the purpose of further obfuscating which cookie is used for session management
  • Includes repeatable form encryption for ajax populated and javascript selected form fields
  • Includes BaseBean with convenience functions for populating primary key data and CSRF fields in urls and forms (respectively)
  • Includes page caching and flushing capabilities added for static views (for NVCFUG Preso) - use url param flushCache to flush
  • Includes fw1 environment control and check for the prod (production) environment before running IP watching or blocking routines
  • Includes configurable block mode - one of abort or redirect. Abort simply aborts further processing for blocked IP's. Redirect works as it did before this release, redirecting to the ipBlocked.html file.
  • Migrated to new Application.cfc FW/1 initialization model
  • Improved HMAC key management to prevent development reloads from forcing the user to re-login (for non-production environments)
  • BREAKING CHANGE The two factor (2FA) authentication code from our two-factor example has been rolled into this code as of 7/24/2017. You can turn on 2FA in the Application.cfc (off by default to maintain backwards compatibility). Code prior to this release has been moved to the legacy branch.
  • BREAKING CHANGE As of 9/12/2017 the keyring master key now uses a PBKDF key on Lucee 5+ and ACF 11+ engines by default instead of legacy hashing to further enhance the security of the keyring. A new function rekeyKeyRing() has been added to the SecurityService to aid in rekeying your keyring for this change (and rekeying it in general) if upgrading from a previous release. You may alternatively uncomment a line in Application.cfc to force legacy master key usage. Please see additional notes in the Application.cfc for further details. Lucee 4.5 will continue to use the legacy hashing of the master key.
  • BREAKING CHANGE The keyring path and the master key are now defined in their own variables in the application scope instead of being hard-coded in the initialization of the security service. These are now BASE64 encoded to aid in obfuscating the key and filename in case of code disclosure. If upgrading from a previous release you will need to BASE64 encode your master keyphrase and filename and replace the new default one in Application.cfc. Please see additional notes in the Application.cfc for further details.
  • BREAKING CHANGE The dashboard controller has removed the rc.product and rc.version variables definitions and the dashboard view now uses the engine and engine version information derived from the application scope
  • There is now an option to use a hacked password list to prevent the system from generating, or user from choosing, a password from the top 100,000 known passwords. This is turned off (false) by default in Application.cfc for backwards compatibility. To use this new function you should set application.rejectHackedPasswords to true.
  • NEW! BREAKING CHANGE The SecurityService.cfc has been enhanced with additional functionality to randomly generate (when creating a new keyring) and use initialization vectors with all encryption and decryption. This will break existing code that is using a keyring without an initialization vector (will return an error about the length of the initialization vector).

This code was put together for the ColdFusion: Code Security Best Practices presentation by Denard Springle at NCDevCon 2015 and has since been transformed into a concise starting point for developers who need to create a secure application using the fw/1 CFML MVC framework.

This code has been expanded multiple times to include additional functionality not shown during the initial presentation. More details on how (and why) these security functions work and are important can be gleaned from reading the ColdFusion Security documents on CFDocs and from reviewing the SecurityService.cfc in /model/services/ which has been expanded for content. The code is ripe with comments to help aid in understanding how and why security features have been implemented and should be easy to pick up and run with for anyone with a passing familiarity of fw/1.

Compatibility

  • Lucee 4.5+

  • NOTE: Not currently compatible with Adobe ColdFusion, but expected to be compatible with the next release.

Installing

  1. Drop the code into your favorite CFML engine's webroot OR install using CommandBox using the command box install fw1-sa
  2. Create a database and generate the users and smsProviders database tables (MSSQL SQL and Excel data provided in the 'database' folder)
  3. Create a datasource called twofactorauth for your database in your CFML engine's admin (or change in Application.cfc)
  4. Configure an object cache, if one is not already defined (or, optionally, add it to Application.cfc if running Lucee 5.x+)
  5. Configure a mail server in your CFML engine's admin
  6. Move the keyrings folder to a location outside your webroot
  7. Modify the default developmentHmacKey value in Application.cfc (use generateSecretKey( 'HMACSHA512' ))
  8. Change the keyRingPath location to where you moved the keyrings folder to in Application.cfc
  9. Change the hash iterations for the hashed keyring file name from the default value of 173 to some other integer number of iterations in Application.cfc
  10. Provide a unique BASE64 encoded value for the application password in Application.cfc (instead of c2VjdXJlX2F1dGhfbWFzdGVyX2tleQ==)
  11. Provide a unique BASE64 encoded value for the application salt in Application.cfc (instead of UnRUcFBBS1hOQmgwem9XYg==)
  12. Provide a unique BASE64 encoded value for the keyring filename in Application.cfc (instead of c2VjdXJlX2F1dGhfa2V5cmluZw==)
  13. Change the hash iterations for the hashed master key from the default value of 512 to some other integer number of iterations in Application.cfc
  14. Change the starting location for the mid() function of the hashed master key to start at a position other than 38 in a range from 1 to 106
  15. Provide unique values for the cookieName and dummyCookieOne, dummyCookieTwo and dummyCookieThree values in Application.cfc
  16. Modify remaining application variables in Application.cfc as needed (see notes in Application.cfc)
  17. Browse to webroot to launch the application and generate a unique set of encryption keys in your keyring
  18. Modify the check if the keyring is a valid array of keys statement in Application.cfc to prevent regeneration of a new keyring file after initial launch. See notes in Application.cfc.
  19. Register an account, login and enjoy!

Upgrading

NOTE If you are currently running a version of fw1-sa without the 2FA integration, then you'll need to complete the following steps before updating to the latest master branch:

If not using 2FA:

  1. Preserve a copy of your existing Application.cfc (or MyApplication.cfc if included in your distribution) so you can copy values for keyring and other application variables as needed.
  2. Modify your users table to include providerId and phone as additional fields before updating

If using 2FA:

  1. Preserve a copy of your existing Application.cfc (or MyApplication.cfc if included in your distribution) so you can copy values for keyring and other application variables as needed.
  2. Modify your users table as above
  3. Add the smsProviders table and import the included data
  4. Assign sms provider id's and phone numbers to existing users (this must be done before switching 2FA on else users will not be able to authenticate)

Bugs and Feature Requests

If you find any bugs or have a feature you'd like to see implemented in this code, please use the issues area here on GitHub to log them.

Contributing

This project is actively being maintained and monitored by Denard Springle. If you would like to contribute to this example please feel free to fork, modify and send a pull request!

Attribution

This project utilizes the free open source MVC CFML (ColdFusion) framework Framework One (fw/1) by Sean Corfield.

License

The use and distribution terms for this software are covered by the Apache Software License 2.0 (http://www.apache.org/licenses/LICENSE-2.0).

framework-one-secure-auth's People

Contributors

ddspringle 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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

framework-one-secure-auth's Issues

Decouple from fw/1 and make a dependency

Instead of updating fw/1 in this repo every time it has a new release, it would be better to decouple it from this project and use CommandBox (or manual install of fw/1) to install it as a dependency.

Requires updating box.json, removing the framework directory and updating README install docs to reflect fw/1 as a pre-requisite when installing manually.

Invalid Login Error

Combine with 2FA example

Instead of having two distinct examples, it's feasible to include configuration options to determine if single or two factor authentication is desired for any particular application and implement each respectively.

Requires migrating 2FA specific functionality to this project, modifying them as required, adding a configuration option for single or two-factor auth selection and firing off appropriate views as needed. Also requires an update to README, and removal of the code in the 2FA project and redirecting users here in that README.

BaseBean issue?

Hi,

Trying to implement this in my app but I am receiving the following error. I am using fw/1 4.2.

Problem with metadata for BaseBean (model.beans.BaseBean) because: Unable to getComponentMetadata(model.beans.BaseBean) because: Invalid CFML construct found on line 100 at column 84. (ColdFusion was looking at the following text:

(

The CFML compiler was processing:

  • An expression beginning with application.securityService.dataEnc, on line 100, column 32.This message is usually caused by a problem in the expressions structure.
  • A script statement beginning with return on line 100, column 25.
  • A script statement beginning with { on line 98, column 50.
  • A script statement beginning with if on line 98, column 17.
  • A script statement beginning with public on line 93, column 9.
), near line 100 in E:\cf\model\beans\BaseBean.cfc

any ideas?

Cannot find any provider supporting BLOWFISH/CTR/PKCS5Padding

I'm not sure how to get the library necessary for this. I tried adding bouncycastle to the lib directory.

154: // using master encryption, encrypt with the master key 155: onePass = encrypt( arguments.value, variables.masterKey, 'AES/CBC/PKCS5Padding', 'HEX' ); 156: lastPass = encrypt( onePass, variables.masterKey, 'BLOWFISH/CTR/PKCS5Padding', 'HEX' ); 157: break; 158:

java.security.NoSuchAlgorithmException

Version Lucee 6.0.1.0
Version Name Gelert
Release date Oct 17, 2023
Label
Installed tag
libraries - Lucee Core Tag Library
Installed function
libraries - Lucee Core Function Library
Remote IP 127.0.0.1
Loader Version 6.0.1.0
Servlet Container WildFly / Undertow - 2.2.28.Final
Java 21.0.2 (Homebrew) 64bit
Host Name 127.0.0.1
OS Mac OS X (14.3.1) 64bit
Architecture 64bit

can't find component [model.beans.User]

When registering a new user, I get the following error window after pressing the Register button:

ERROR!
An error occurred!

Action: home:main.process
Error: invalid component definition, can't find component [model.beans.User]
Type: expression
Details:

Diagnose member function issue

Found 'No matching Method/Function for Number.len() found' error on L112 and L223 ( arguments.value.len() ) when testing commit 2e240b8 w/ fw/1 4.0.0 on Lucee 5.1.0.34

Attempted rename of arguments.value to other names (arguments,input, arguments.data) to see if this was a naming issue, but error persisted.

Currently running same SecurityService in Lucee 4.5.4.017 using CB 4.3.0+188 w/o this issue.

Need to isolate if this is a Lucee 5.1.0.34 issue, a fw/1 4.0.0 issue, some combination of those two or something else entirely.

Typo in UserService.cfc - Line 99

Typo in model/services/UserService.cfc - Line 99
ending '.' (period) should be ',' (comma).

This assumes I'm looking at the most recent version and not an idiot. I must add, a lot of your code makes me feel like an idiot. Thanks and nicely done.

typo-userservice cfc

Wrong IV length: must be 16 bytes long

Such an error will display after registration form is submitted.

Environment: Lucee 5.2.7+63 (Commanbox) + MySQL 5.7

StackTrace

lucee.runtime.exp.NativeException: Wrong IV length: must be 16 bytes long at 
com.sun.crypto.provider.CipherCore.init(CipherCore.java:516) at 
com.sun.crypto.provider.AESCipher.engineInit(AESCipher.java:339) at 
javax.crypto.Cipher.implInit(Cipher.java:806) at 
javax.crypto.Cipher.chooseProvider(Cipher.java:864) at 
javax.crypto.Cipher.init(Cipher.java:1396) at 
javax.crypto.Cipher.init(Cipher.java:1327) at 
lucee.runtime.crypt.Cryptor._crypt(Cryptor.java:132) at 
lucee.runtime.crypt.Cryptor.crypt(Cryptor.java:63) at 
lucee.runtime.crypt.Cryptor.encrypt(Cryptor.java:155) at 
lucee.runtime.crypt.Cryptor.encrypt(Cryptor.java:170) at 
lucee.runtime.functions.other.Encrypt.invoke(Encrypt.java:68) at 
lucee.runtime.functions.other.Encrypt.call(Encrypt.java:50) at 
model.services.securityservice_cfc$cf.udfCall1(/model/services/SecurityService.cfc:135) at 
model.services.securityservice_cfc$cf.udfCall(/model/services/SecurityService.cfc) at 
lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107) at 
lucee.runtime.type.UDFImpl._call(UDFImpl.java:357) at 
lucee.runtime.type.UDFImpl.call(UDFImpl.java:226) at 
lucee.runtime.ComponentImpl._call(ComponentImpl.java:687) at 
lucee.runtime.ComponentImpl._call(ComponentImpl.java:567) at 
lucee.runtime.ComponentImpl.call(ComponentImpl.java:1988) at 
lucee.runtime.util.VariableUtilImpl.callFunctionWithoutNamedValues(VariableUtilImpl.java:756) at 
lucee.runtime.PageContextImpl.getFunction(PageContextImpl.java:1718) at 
home.controllers.main_cfc$cf.udfCall(/home/controllers/main.cfc:134) at 
lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107) at
lucee.runtime.type.UDFImpl._call(UDFImpl.java:357) at 
lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212) at lucee.runtime.ComponentImpl._call(ComponentImpl.java:689) at lucee.runtime.ComponentImpl._call(ComponentImpl.java:567) at 
lucee.runtime.ComponentImpl.callWithNamedValues(ComponentImpl.java:2005) at 
lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:869) at 
lucee.runtime.functions.dynamicEvaluation.Invoke.call(Invoke.java:50) at 
framework.one_cfc$cf.udfCalla(/framework/one.cfc:1629) at 
framework.one_cfc$cf.udfCall(/framework/one.cfc) at 
lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107) at 
lucee.runtime.type.UDFImpl._call(UDFImpl.java:357) at 
lucee.runtime.type.UDFImpl.call(UDFImpl.java:226) at 
lucee.runtime.type.scope.UndefinedImpl.call(UndefinedImpl.java:771) at 
lucee.runtime.util.VariableUtilImpl.callFunctionWithoutNamedValues(VariableUtilImpl.java:756) at 
lucee.runtime.PageContextImpl.getFunction(PageContextImpl.java:1718) at 
framework.one_cfc$cf.udfCall6(/framework/one.cfc:890) at 
framework.one_cfc$cf.udfCall(/framework/one.cfc) at 
lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107) at 
lucee.runtime.type.UDFImpl._call(UDFImpl.java:357) at 
lucee.runtime.type.UDFImpl.call(UDFImpl.java:226) at 
lucee.runtime.ComponentImpl._call(ComponentImpl.java:687) at 
lucee.runtime.ComponentImpl._call(ComponentImpl.java:567) at 
lucee.runtime.ComponentImpl.call(ComponentImpl.java:1988) at 
lucee.runtime.util.VariableUtilImpl.callFunctionWithoutNamedValues(VariableUtilImpl.java:756) at 
lucee.runtime.PageContextImpl.getFunction(PageContextImpl.java:1718) at 
application_cfc$cf.udfCall(/Application.cfc:298) at 
lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107) at 
lucee.runtime.type.UDFImpl._call(UDFImpl.java:357) at 
lucee.runtime.type.UDFImpl.call(UDFImpl.java:226) at 
lucee.runtime.ComponentImpl._call(ComponentImpl.java:687) at 
lucee.runtime.ComponentImpl._call(ComponentImpl.java:567) at lucee.runtime.ComponentImpl.call(ComponentImpl.java:1988) at 
lucee.runtime.listener.ModernAppListener.call(ModernAppListener.java:424) at 
lucee.runtime.listener.ModernAppListener._onRequest(ModernAppListener.java:223) at 
lucee.runtime.listener.MixedAppListener.onRequest(MixedAppListener.java:43) at 
lucee.runtime.PageContextImpl.execute(PageContextImpl.java:2464) at 
lucee.runtime.PageContextImpl._execute(PageContextImpl.java:2454) at 
lucee.runtime.PageContextImpl.executeCFML(PageContextImpl.java:2427) at 
lucee.runtime.engine.Request.exe(Request.java:44) at 
lucee.runtime.engine.CFMLEngineImpl._service(CFMLEngineImpl.java:1091) at 
lucee.runtime.engine.CFMLEngineImpl.serviceCFML(CFMLEngineImpl.java:1039) at 
lucee.loader.engine.CFMLEngineWrapper.serviceCFML(CFMLEngineWrapper.java:102) at 
lucee.loader.servlet.CFMLServlet.service(CFMLServlet.java:51) at 
javax.servlet.http.HttpServlet.service(HttpServlet.java:790) at 
io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74) at 
io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129) at 
org.cfmlprojects.regexpathinfofilter.RegexPathInfoFilter.doFilter(RegexPathInfoFilter.java:47) at 
io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) at 
io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) at 
io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84) at 
io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62) at 
io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:64) at 
io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36) at 
io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:132) at 
io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57) at 
io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) at 
io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46) at 
io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64) at 
io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60) at 
io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77) at 
io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43) at 
io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) at 
io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) at 
io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:292) at io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:81) at 
io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:138) at 
io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:135) at 
io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48) at 
io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43) at 
io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:272) at 
io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:81) at 
io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:104) at 
io.undertow.server.Connectors.executeRootHandler(Connectors.java:336) at 
io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830) at 
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at 
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at 
java.lang.Thread.run(Thread.java:745) Caused by: java.security.InvalidAlgorithmParameterException: Wrong IV length: must be 16 bytes long ... 101 more

application.securityService
screen shot 2018-07-23 at 21 13 57

As shown above, the encryptionIV1 is 12-byte long (B0O9PAmQSxo=). If I append four equality signs to it to make it 16-byte long (B0O9PAmQSxo=====), then dataEnc() will pass. But I doubt it's feasible to hard-code a number of 16 here.

screen shot 2018-07-23 at 21 14 18

Any insights will be appreciated. :)

Implement new Application.cfc fw/1 initialization

fw/1 is no longer required to be extended and can instead be included in the Application.cfc within the appropriate functions. As a better example of how to use fw/1 in a modern way the Application.cfc should be rewritten to use this new method of framework invocation.

Fix ACF incompatibilities

While testing the code on ACF recently I realized there's a few differences between Lucee and ACF that I did not account for. This issue is to remind me to make this ACF compatible again by the next release of ACF.

Aggregate IP watching and blocking functions

There is a lot of overlap between the IP Blocking and IP Watching functionality - specifically adding, removing, reading, writing and importing functions. Aggregate those sets of functions to single functions that handle both IP watching and IP blocking.

Setting cookies doesn't work in CommandBox 3.4.0+00517

getPageContext().getResponse().addHeader("Set-Cookie"...

AND

cfcookie(...)

both fail to set a cookie when using fw1-sa within CommandBox.

I'd previously run into this when building out a project for a client and the only solution I could find, less than ideal, was to use:

cookie.blah = ...

I'll have to see if I can snake some of @bdw429s time to see if there is a better general workaround when using fw/1 under CommandBox for setting cookies w/ all the fixings (domain, path, expires, HTTPOnly, etc.)

db vs repeatable

In your security service cfc you use repeatable and db repeatable encryption. What are the differences/use cases for those?

And do you have examples for how you're using the form/url encryption methods? I would like to encrypt/hash form/url field names too and I'm looking for best practices.

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.