module Tokens module ActiveRecord def self.included(base) base.class_eval { extend ClassMethods } end module Serializer class << self # Set the serializer adapter. Defaults to JSON. attr_accessor :adapter end require "json" self.adapter = ::JSON def self.load(data) data ? JSON.load(data) : {} end def self.dump(data) data ? JSON.dump(data) : nil end end module ClassMethods # Set up model for using tokens. # # class User < ActiveRecord::Base # tokenizable # end # def tokenizable has_many :tokens, as: "tokenizable", dependent: :destroy include InstanceMethods end # Generate token with specified length. # # User.generate_token(10) # def generate_token(size) validity = Proc.new {|token| Token.where(:token => token).first.nil?} begin token = SecureRandom.hex(size)[0, size] token = token.encode("UTF-8") end while validity[token] == false token end # Find a token # # User.find_token(:activation, "abcdefg") # User.find_token(name: activation, token: "abcdefg") # User.find_token(name: activation, token: "abcdefg", tokenizable_id: 1) # def find_token(*args) if args.first.kind_of?(Hash) options = args.first else options = { name: args.first, token: args.last } end options.merge!(name: options[:name].to_s, tokenizable_type: self.name) Token.where(options).includes(:tokenizable).first end # Find object by token. # # User.find_by_token(:activation, "abcdefg") # def find_by_token(name, hash) token = find_token(name: name.to_s, token: hash) return unless token token.tokenizable end # Find object by valid token (same name, same hash, not expired). # # User.find_by_valid_token(:activation, "abcdefg") # def find_by_valid_token(name, hash) token = find_token(name: name.to_s, token: hash) return if !token || token.expired? token.tokenizable end end module InstanceMethods # Verify if given token is valid. # # @user.valid_token?(:active, "abcdefg") # def valid_token?(name, hash) self.tokens.where(name: name.to_s, token: hash.to_s).first != nil end # Find a token. # # @user.find_token(:activation, "abcdefg") # def find_token(name, token) self.class.find_token( tokenizable_id: self.id, name: name.to_s, token: token ) end # Find token by its name. def find_token_by_name(name) self.tokens.where(name: name.to_s).first end # Return Token instance when token is valid. def find_valid_token(name, token) token = find_token(name, token) return unless token !token.expired? && token end # Remove token. # # @user.remove_token(:activate) # def remove_token(name) return if new_record? token = find_token_by_name(name) token && token.destroy end # Add a new token. # # @user.add_token(:api_key, token: 'abc123') # @user.add_token(:api_key, expires_at: nil) # @user.add_token(:api_key, size: 20) # @user.add_token(:api_key, data: {when: Time.now}) # def add_token(name, options={}) options.reverse_merge!({ expires_at: 2.days.from_now, size: 12, data: nil }) remove_token(name) attrs = { name: name.to_s, token: options[:token] || self.class.generate_token(options[:size]), expires_at: options[:expires_at], data: options.fetch(:data) || {} } self.tokens.create!(attrs) end end end end