Giter Site home page Giter Site logo

maxjustus / sinatra-authentication Goto Github PK

View Code? Open in Web Editor NEW
476.0 19.0 94.0 636 KB

A sinatra extension wrapped in a gem that implements authentication/permissions with users stored in the database. Now with optional support for facebook connect

License: The Unlicense

Ruby 99.09% Shell 0.91%

sinatra-authentication's Introduction

A little sinatra gem that implements user authentication, with support for Datamapper, Mongomapper, Mongoid, Sequel and Rufus-Tokyo

INSTALLATION:

in your sinatra app simply require either "dm-core", 'sequel', 'rufus-tokyo', 'mongoid' or "mongo_mapper", "digest/sha1", 'rack-flash' (if you want flash messages) and then "sinatra-authentication" and turn on session storage with a super secret key, like so:

require "dm-core"
#for using auto_migrate!
require "dm-migrations"
require "digest/sha1"
require 'rack-flash'
require "sinatra-authentication"

use Rack::Session::Cookie, :secret => 'A1 sauce 1s so good you should use 1t on a11 yr st34ksssss'
#if you want flash messages
use Rack::Flash

If you're using rufus-tokyo, you also need to set the database path for Users. like so:

require "rufus_tokyo"
require "digest/sha1"
require 'rack-flash'
require "sinatra-authentication"

#Setting the database path for Users
TcUserTable.cabinet_path = File.dirname(__FILE__) + 'folder/where/you/wanna/store/your/database'

use Rack::Session::Cookie, :secret => 'A1 sauce 1s so good you should use 1t on a11 yr st34ksssss'
#if you want flash messages
use Rack::Flash

DEFAULT ROUTES:

  • get '/login'
  • get '/logout'
  • get '/signup'
  • get/post '/users'
  • get '/users/:id'
  • get/post '/users/:id/edit'
  • get '/users/:id/delete'

If you fetch any of the user pages using ajax, they will automatically render without a layout

ADDITIONAL ROUTES WHEN USING SINBOOK FOR FACEBOOK INTEGRATION:

  • get '/receiver'
  • get '/connect'

FLASH MESSAGES

Flash messages are implemented using rack-flash. To set them up add this to your code:

require 'rack-flash'

#be sure and do this after after 'use Rack:Session:Cookie...'
use Rack::Flash

And then sinatra-authentication related flash messages will be made available through flash[:notice] (successes) and flash[:error] (failures)

-# somewhere in a haml view:
= flash[:notice]
= flash[:error]

HELPER METHODS:

This plugin provides the following helper methods for your sinatra app:

  • login_required

    which you place at the beginning of any routes you want to be protected

  • current_user
  • logged_in?
  • render_login_logout(html_attributes)

    Which renders login/logout and singup/edit account links. If you pass a hash of html parameters to render_login_logout all the links will get set to them. Which is useful for if you're using some sort of lightbox

SIMPLE PERMISSIONS:

By default the user class includes a method called admin? which simply checks if user.permission_level == -1.

you can take advantage of this method in your views or controllers by calling current_user.admin? i.e.

- if current_user.admin?
  %a{:href => "/adminey_link_route_thing"} do something adminey

(these view examples are in HAML, by the way)

You can also extend the user class with any convenience methods for determining permissions. i.e.

#somewhere in the murky depths of your sinatra app
class User
  def peasant?
    self.permission_level == 0
  end
end

then in your views you can do

- if current_user.peasant?
  %h1 hello peasant!
  %p Welcome to the caste system! It's very depressing.

if no one is logged in, current_user returns a GuestUser instance, which responds to current_user.guest? with true, current_user.permission_level with 0 and any other method calls with false

This makes some view logic easier since you don't always have to check if the user is logged in, although a logged_in? helper method is still provided

RUFUS TOKYO

when using rufus-tokyo, current_user returns a hash, so to get the primary key of the current_user you would do current_user[:pk]. if you wanna set an attribute, you can do something like current_user["has_a_dog"] = true and if you want to open a connection with the cabinet directly, you can do something like

user_connection = TcUser.new
users_with_gmail = user_connection.query do |q|
  q.add 'email', :strinc, 'gmail'
end
user_connection.close

FACEBOOK

at present, sinatra authentication supports sinbook for interacting with the facebook api.

If you want to allow users to login using facebook, just require 'sinbook' before requiring 'sinatra-authentication'. The routes '/receiver' and '/connect' will be added. as well as connect links on the login and edit account pages. You'll still have to include and initialize the facebook connect javascript in your layout yourself, like so:

(This example layout assumes you're using sinbook)

!!!
  %head
    %title Welcome to my Facebook Connect website!
    %script{:type => 'text/javascript', :src => 'http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php/en_US'}
  %body
    = yield
    :javascript
      FB.init("#{fb.api_key}", "/receiver")

Just remember to specify '/receiver' as the path to the xd-receiver file in your call to 'FB.init'.

The render_login_logout helper 'logout' link will log the user out of facebook and your app.

I've also included a little helper method 'render_facebook_connect_link' for rendering the facebook connect link with the correct 'onconnect' javascript callback. The callback redirects to '/connect'. This is important because the way I've implemented facebook connect support is by pinging '/connect' after the user successfully connects with facebook.

If you choose to render the connect button yourself, be sure to have the 'onconnect' callback include "window.location = '/connect'".

'/connect' redirects to '/' on completion.

The 'render_facebook_connect_link' helper uses html instead of fbml, so ajax requests to '/login' or "/users/#{user.id}/edit" will render the connect link without you needing to parse any fbml.

If the user is already logged into the app and connects with facebook via the user edit page, it adds their fb_uid to their profile in the database, which will allow them to log in using their email and password, OR their facebook account.

If they aren't already logged in to the app through the normal login form, it creates a new user in the database without an email address or password. They can later add this data by going to "/users/#{current_user.id}/edit", which will allow them to log in using their email address and password, OR their facebook account.

OVERRIDING DEFAULT VIEWS

Right now if you're going to override sinatra-authentication's views, you have to override all of them. This is something I hope to change in a future release.

To override the default view path do something like this:

set :sinatra_authentication_view_path, Pathname(__FILE__).dirname.expand_path + "my_views/"

And then the views you'll need to define are:

  • show.haml
  • index.haml
  • signup.haml
  • login.haml
  • edit.haml

To override haml, set template_engine in your Sinatra App:

configure do
    set :template_engine, :erb # for example
end

The signup and edit form fields are named so they pass a hash called 'user' to the server:

%input{:name => "user[email]", :size => 30, :type => "text", :value => @user.email}
%input{:name => "user[password]", :size => 30, :type => "password"}
%input{:name => "user[password_confirmation]", :size => 30, :type => "password"}

%select{:name => "user[permission_level]"}
  %option{:value => -1, :selected => @user.admin?}
    Admin
  %option{:value => 1, :selected => @user.permission_level == 1}
    Authenticated user

if you add attributes to the User class and pass them in the user hash your new attributes will be set along with the others.

The login form fields just pass a field called email and a field called password:

%input{:name => "email", :size => 30, :type => "text"}
%input{:name => "password", :size => 30, :type => "password"}

To add methods or properties to the User class, you have to access the underlying database user class, like so:

class DmUser
  property :name, String
  property :has_dog, Boolean, :default => false
end

And then to access/update your newly defined attributes you use the User class:

current_user.name
current_user.has_dog

current_user.update({:has_dog => true})

new_user = User.set({:email => '[email protected]' :password => 'hi', :password_confirmation => 'hi', :name => 'Max', :has_dog => false})

User.all(:has_dog => true).each do |user|
  user.update({has_dog => false})
end

User.all(:has_dog => false).each do |user|
  user.delete
end

the User class passes additional method calls along to the interfacing database class, so most calls to Datamapper/Sequel/Mongomapper/RufusTokyo functions should work as expected.

If you need to get associations on current_user from the underlying ORM use current_user.db_instance, take this case for example: class Brain include DataMapper::Resource property :type, String property :iq, Integer end

class DmUser
  has n, :brains
end

get '/' do
  @user_brains = current_user.db_instance.brains
end

The database user classes are named as follows:

  • for Datamapper:

    DmUser

  • for Sequel:

    SequelUser

  • for Rufus Tokyo:

    TcUser

  • for Mongoid:

    MongoidUser

  • for Mongomapper:

    MmUser

Deprecations

  • All database adapters now store created_at as a Time object.

Known issues

  • First user in database is not properly recognized as site admin

    Proposed fix: add site_admin_email option when initialization functionality is added

Roadmap

  • Move database adapter initialization, along with auto configuration of sinbook and rack flash functionality into a Sinatra::SinatraAuthentication.init(args) method
  • Refactor/redesign database adapter interface, make User class AbstractUser and all ORM user classes User, with corresponding specs
  • Remove Facebook connect support and add support for Omniauth
  • Provide a method for overriding specific views, and/or specifying your own form partial, (passed an instance of User)
  • Add Remember me (forever) checkbox to login form
  • Add next url parameter support for login/signup
  • Add verb selection on configuration (Sign in / Log in)
  • Provide optional support through init method for inclusion of username

    Where login form accepts either email or username (through the same field)

  • Add email functionality

    Confirmation emails Forgotten password emails

  • Look into what might be neccesary to allow for logging in using Ajax

Maybe

  • Allow passing custom database attributes into init method, also dynamically altering corresponding signup and user edit views. (potentially leaky abstraction)

    As an alternative, create a generic interface for accessing database row names through the various ORMs. So when users alter their User schemas, I can make my views 'Just Work'.

  • Add HTTP basic auth support
  • Add pluggable OAuth consumer/provider support

License

This software is released under the Unlicense. See the UNLICENSE file in this repository or http://unlicense.org for details.

sinatra-authentication's People

Contributors

abhas avatar britg avatar dmeiz avatar giovanigenerali avatar gogolok avatar heliosmaster avatar jamesdouma avatar jesper avatar jmervine avatar jphastings avatar jrimmer avatar kazuhiro avatar markphelps avatar maxjustus avatar mheffner avatar spk avatar timmyc avatar wa9ace 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

sinatra-authentication's Issues

Testing authentication

I am struggling with how to test authentication using RSpec 2.0. I can verify that authentication and signup works manually, but in my spec I run in to some problems. I create users in the database, and try to manually assign my create user id to the session variable, like so:

session[:user] = @user.id

I have verified that @user exists and is valid. However, I get the following error:

NameError:
undefined local variable or method `session' for #<RSpec::Core::ExampleGroup::Nested_1::Nested_1:0x000001019967c0>

How can I log users in programatically in my tests? I am including my "app.rb" file that includes sinatra-authentication and the rest of my gems, so I don't know why "session" doesn't exist.

@huboard:{"order":10.75}

settings

Should move to 'settings', as 'options' is being deprecated (Sinatra::Base)

jquery example.

could you make an example for using mongoid and ajax for the login?

id instead of _id with dm-core

I'm using this authentication system with datamapper over mongo-db.
Signup works but insert a dm-user with two id field:

  • "_id" that contains the identifier of the records
  • "id" which is null.

During a log in, if the user and password are correct, the user is logged in as the user nil, and is consequently staying a guest.

Edit: I fixed my particular problem by overriding the DmUsers class and replacing Serial by the type: ObjectId and by adding "require 'mongo_adapter'" which include the support for that specific mongo type

using sinatra_authentication with padrino

Hi max...I'm trying use your authentication system with padrino, padrino is a nice and simple framework based in sinatra...so...I thought this would works with padrino..but I don't get this work..maybe you can help me or you may say me whats changes must I do...thanks a lot....

No files or directories folder/views/auth/signup.haml

Why does sinatra-authenticatetion not detect my template engine configuration?

require 'sinatra/base'
require 'sinatra/flash'
require "sinatra-authentication"
require 'slim'

class ApplicationController < Sinatra::Base
  #if you want flash messages
  register Sinatra::Flash
  register Sinatra::SinatraAuthentication
  use Rack::Session::Cookie, :secret => "sha1h1fsa23xs1j6g436gew5"
  # => config
  configure do
    set :sinatra_authentication_view_path, Pathname(__FILE__).dirname.expand_path + "../views/auth/"
    set :template_engine, :slim
    set :admin_username, 'frank'
    set :admin_password, 'sinatra'
    set :public_folder, 'public'
    set :views, 'views'
    enable :show_exceptions
  end

What license is sinatra-authentication?

May I ask what license sinatra-authentication is released as? I'm considering extending or adding to this project when I have time, but without a license file I'm not sure. (Sorry to be so asinine like that, I just like to stay safe.)

I would assume this project is MIT like most ruby projects?

Overriding template engine not possible

Hi,

i have designed my sinatra app as a standalone-app and not as a subclass of Sinatra::Application.
However, the setting for overriding the template engine from haml to erb is not working for me.
I have made the setting

configure do
   set :template_engine, :erb
end

and still get the following error:

LoadError at /login
cannot load such file -- haml

Validation with mongo mapper

I'm using mongo mapper. I'm able to create users, but it's not returning errors when you don't use a unique email or don't enter passwords on signup.

New database backend: Treequel

Hi!

I would like to implement a Treequel-based LDAP backend for managing and authenticating users. Any pointers on how I could get started with this? Subsequently, I will send across a pull request for the same.

Cheers, Abhas.

PS. Treequel => https://github.com/ged/treequel

can't convert DmUser to Array

Hi - sorry can't figure this one out.

I've associated the DmUser class with a customer class (i.e. Users have many Customers) like so:

class DmUser
  has n, :customers
end
class Customer
  include DataMapper::Resource

  property :id,  Serial
  property :name, String

  belongs_to :dm_user
end

When attempting to call the customers with the following in main.rb:

get '/dashboard'
  @customers = current_user.db_instance.customers
  haml :dash
end

and the following line in dash.haml:

- @customers.each {|c| puts c }

I get the error:

TypeError at /dashboard
can't convert DmUser to Array (DmUser#to_ary gives FalseClass)

Any ideas?

User class confusion

I'm using sinatra-authentication with Mongo Mapper, but the method_missing way of accessing the underlying Mongo class isn't quite enough for accessing some of MM's features eg:

class MmUser
  many :addresses
end

class Address
  key :street, String
end

# This works
p current_user.addresses

# This doesn't
current_user.addresses << Address.new(:street => 'mine')
current_user.save

Is there a way to access the underlying class? Could the User class be configured to use delegate rather than method_missing?
@huboard:{"order":10.5}

Email is not returned in Callback data

I just create the new app on the FB developer center and tried FB login with this plugin.
The login is working properly, but I can't get email as callback data.
Is there any idea who can solve this?

doesn't work with mongoid > 3.0.0

The mongoid_adapter, in the get(hash) method definition, calls first() with arguments. It looks like the mongoid api has changed in 3.0 and now first doesn't take any argument anymore

Setting sinatra_authentication_view_path not working

The method to override default views as described here (https://github.com/maxjustus/sinatra-authentication#overriding-default-views) does not seem to be working correctly.

I have the following directory structure...

/app
  /views
    edit.haml
    index.haml
    layout.haml
    login.haml
    show.haml
    signup.haml
  app.rb

...with this line of code in my app.rb file...

set :sinatra_authentication_view_path, Pathname(__FILE__).dirname + "views/"

...and the haml templates in /views are not being used.

Issues with Mongoid 3?

Hi there,

Been trying to use sinatra-authentication in a very simple test app with mongoid3 and have been running into some odd problems. Probably operator error, but worth mentioning, I thought.

When using mongoid 3, I often get errors like:
ArgumentError at /login
wrong number of arguments (1 for 0)

an easy way to get this is to enter bad credentials into the login screen. Another way to get it is to try to access current_user after a successful login. The same behavior is seen in 3.0.0-3.0.3 but the problems all go away when I switch to mongoid 2.4.12. I am using Ruby 1.9.3

Here is a paste: http://pastebin.com/TzEaWKPa

It looks like one of the problems is in line 14 of mongoid_adapter.rb, which is:
MongoidUser.first(:conditions => hash)

It looks as though the proper usage in Mongoid 3 is
MongoidUser.find(:conditions => hash).first

Rack-flash

Rack-flash was not working for me (neither rack-flash3) so I just used sinatra-flash instead.

overwrite /signup and create /users/new

I really like this gem and I use it also for my latest project.

Meanwhile I need to deactivate the /signup route and give admin users the possibility to create users. Letting guests create accounts is a security flaw for my application.

What I did was to migrate from a classic app to a modular app, so I was able to overwrite /signup.

But while creating post /users I get stuck:
NoMethodError at /users
undefined method `include?' for nil:NilClass

Can someone help me with the User model?

register Sinatra::SinatraAuthentication # load auth

post '/users' do
@user = User.set(params[:user])
if @user.valid && @user.id
session[:user] = @user.id
if Rack.const_defined?('Flash')
flash[:notice] = "Account created."
end
redirect '/'
else
if Rack.const_defined?('Flash')
flash[:error] = "There were some problems creating the account: #{@user.errors}."
end
redirect '/users/new' + hash_to_query_string(params['user'])
end
end

Use rack-flash3 instead of rack-flash

There are problems with the latest release of Rack and rack-flash. Rack-Flash3 addresses these issues, but sinatra-authentication does not recognize it as a replacement for rack-flash.

The DM example and validations are going to be deprecated

Since the latest version of DM, same validations names has change and the ones used in the plugin are deprecated.
Also, the example is not working because, the latest DM has the auto_migrate! method in dm-migrations instead of dm-core.

Forgot password

I saw on the roadmap that "forgot password" is on the roadmap, but I'd like to place a vote for bumping it up the line.

Missing/Wrong Views?

I am attempting to write a small blogging engine for myself.

I have gotten sinatra, mongoid, and haml all working but when I visit any sinatra-authentication page nginx throws an internal server error.

this is the error I am getting
Errno::ENOENT - No such file or directory - /opt/nginx/html/raptor.patrickarlt.com/views/layout.haml:

you can see all my files here https://gist.github.com/854156

get '/' works confirming Sinatra is working
get '/haml' works confirming haml is working
get '/private' redirects to '/login' confirming sinatra-authentication is working
get '/login' internal server error

Ruby 1.9.2
Nginx 0.8.54
Passenger 3.0.2

Problems with sinatra-authentication after rubygems upgrade...

I just upgraded ruby gems today and now my sinatra application won't start. It fails while loading the sinatra-authentication gem. Is there something that I can do to fix this?

Thanks in advance,

Abhas.

/usr/local/ruby/lib/ruby/site_ruby/1.9.1/rubygems.rb:1090:in `escape': can't convert Pathname to String (TypeError)
    from /usr/local/ruby/lib/ruby/site_ruby/1.9.1/rubygems.rb:1090:in `block in loaded_path?'
    from /usr/local/ruby/lib/ruby/site_ruby/1.9.1/rubygems.rb:1089:in `each'
    from /usr/local/ruby/lib/ruby/site_ruby/1.9.1/rubygems.rb:1089:in `find'
    from /usr/local/ruby/lib/ruby/site_ruby/1.9.1/rubygems.rb:1089:in `loaded_path?'
    from /usr/local/ruby/lib/ruby/site_ruby/1.9.1/rubygems/custom_require.rb:35:in `require'
    from /usr/local/ruby/lib/ruby/gems/1.9.1/gems/activesupport-3.0.3/lib/active_support/dependencies.rb:239:in `block in require'
    from /usr/local/ruby/lib/ruby/gems/1.9.1/gems/activesupport-3.0.3/lib/active_support/dependencies.rb:225:in `block in load_dependency'
    from /usr/local/ruby/lib/ruby/gems/1.9.1/gems/activesupport-3.0.3/lib/active_support/dependencies.rb:596:in `new_constants_in'
    from /usr/local/ruby/lib/ruby/gems/1.9.1/gems/activesupport-3.0.3/lib/active_support/dependencies.rb:225:in `load_dependency'
    from /usr/local/ruby/lib/ruby/gems/1.9.1/gems/activesupport-3.0.3/lib/active_support/dependencies.rb:239:in `require'
    from /usr/local/ruby/lib/ruby/gems/1.9.1/gems/sinatra-authentication-0.4.1/lib/sinatra-authentication.rb:3:in `<top (required)>'
    from /usr/local/ruby/lib/ruby/site_ruby/1.9.1/rubygems/custom_require.rb:57:in `require'
    from /usr/local/ruby/lib/ruby/site_ruby/1.9.1/rubygems/custom_require.rb:57:in `rescue in require'
    from /usr/local/ruby/lib/ruby/site_ruby/1.9.1/rubygems/custom_require.rb:35:in `require'
    from /usr/local/ruby/lib/ruby/gems/1.9.1/gems/activesupport-3.0.3/lib/active_support/dependencies.rb:239:in `block in require'
    from /usr/local/ruby/lib/ruby/gems/1.9.1/gems/activesupport-3.0.3/lib/active_support/dependencies.rb:225:in `block in load_dependency'
    from /usr/local/ruby/lib/ruby/gems/1.9.1/gems/activesupport-3.0.3/lib/active_support/dependencies.rb:596:in `new_constants_in'
    from /usr/local/ruby/lib/ruby/gems/1.9.1/gems/activesupport-3.0.3/lib/active_support/dependencies.rb:225:in `load_dependency'
    from /usr/local/ruby/lib/ruby/gems/1.9.1/gems/activesupport-3.0.3/lib/active_support/dependencies.rb:239:in `require'
    from index.rb:13:in `<main>'

current_user not being created

I'm using the latest versions of DM, Sinatra and sinatra-authentication and I am perplexed by a recurrent problem with the current_user object not being created. This causes a server error when I have -if current_user.admin? in my templates (no method 'admin' for nil:nilClass).

When it occurs, the only predictable thing I can do to get my site back running is remove all references to current_user from my templates. The /login and /logout pages still work. The problem seems to go away and come back at will; I haven't yet detected the pattern.

Can anyone think of what might prevent current_user from being created? I am a novice at this so I may be missing something obvious.

Thankyou muchly.

Flash message marked as 'notice', when it should be 'error'.

I'm not sure why this is happening, as it seems to be correct in the current code here, but the login error "The email or password you entered is incorrect." is being marked as ':notice', instead of ':error' in RackFlash. The content of the Rack::FLash object follows:

'#FlashHash @values={:notice=>"The email or password you entered is incorrect."} @cache={}'

Using the database outside of sinatra-authenticate

If I want to build a cron job that works on the same database as your sinatra-authentication has auto-manufactured I can't just require 'sinatra-authentication' and let it take care of everything for me because that requires sinatra.

It would be nice if I could have, say, only the following lines:

require 'mongo_mapper'
require 'sinatra-authentication/models'

in a non-sinatra script (eg. environment.rb), and have the MmUser class prepped with all the same keys as it would in my sinatra app.


Hehe, I got a little carried away testing out how this could be done and made something that works! See what you think: jphastings/sinatra-authentication@ee84251

XSRF / CSRF

There seems to be little or no protection for XSRF / CSRF.

Users not created

Is there specific version of DataMapper or sqlite / mysql gems needed. I have a problem of new user not being created in database after submitting signup form. I have tested with both MySQL and SQLite. Using the default views for forms. No errors. No anything.

I just can figure out what causes the problem :)

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.