Skip to content

The simple login/logout management for Responder by kennethreitz

License

Notifications You must be signed in to change notification settings

nagataaaas/responder-login

Repository files navigation

Reponder-Login

The Simple Login/Logout Management for Responder

import responder
from responder_login import LoginManager

api = responder.API()
lm = LoginManager(api)

@api.route("/login_required")
@lm.login_required
def login_required(req, resp):
    resp.text = "You can't see this without logging in!"

Powered by Yamato Nagata

Simple Example

ReadTheDocs

The basic idea is based on Flask-Login

Usage

make instance of LoginManager with responder.API

import responder
from responder_login import LoginManager

api = responder.API()
lm = LoginManager(api)

You will use this instance for almost all of Responder-Login features.

Next, you need to make a class you want to use in login/logout. in this example, I use SQLAlchemy

from responder_login import UserMixin

from sqlalchemy.orm import scoped_session
from sqlalchemy.orm.session import sessionmaker
import sqlalchemy.ext.declarative

Base = sqlalchemy.ext.declarative.declarative_base()
url = "sqlite:///database.db"


class Student(Base, UserMixin):
    __tablename__ = "students"
    id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
    name = sqlalchemy.Column(sqlalchemy.String(20))
    grade = sqlalchemy.Column(sqlalchemy.Integer)
    password = sqlalchemy.Column(sqlalchemy.String(20))  # Please encrypt if you use :)

    def get_id(self):
        return str(self.id)


engine = sqlalchemy.create_engine(url, echo=False)
Base.metadata.create_all(engine)
SessionMaker = sessionmaker(bind=engine)
session = scoped_session(SessionMaker)

All you have to understand in what I done in above, is making class inheriting LoginManager.UserMixin and make attribute get_id.

Then you need to set callback which specifies User Object.

@lm.user_loader
def load_user(user_id):
    return session.query(Student).get(user_id)

The callback you set to LoginManager.user_loader must return a User object or None. Do not raise any Exceptions.

Once you made user object and set LoginManager.user_loader, you can use all features of Responder-Login.

The simple example is Here

Documentation

LoginManager(api=None)

Initialize LoginManager.api must be an instance of Responder.API.

api can be None or not provided. But, then you have to execute LoginManager.init_api()

LoginManager.init_api(api)

Set Responder.API with given api. This will set current_user to jinja2 environment variable.

@LoginManager.user_loader

This decorates callback to set it LoginManager._user_callback.

callback must take one argument and return instance of User object or None.

@LoginManager.login_required

The decorator which decorates Responder.route callback.

If user want to access decorated route but user must log in, this will call LoginManager._unauthorized_callback if it's provided.

That can be set by decorating callback with LoginManager.unauthorized_handler which takes Request and Response. If LoginManager._unauthorized_callback isn't provided, this will redirect to LoginManager.config["LOGIN_REQUIRED_ROUTE"] and HTTP Status Code will be set to 307 if it's set.

If not, set LoginManager.config["LOGIN_REQUIRED_MESSAGE"] to resp.text.

@LoginManager.unauthorized_handler

This decorates callback to set it LoginManager._unauthorized_callback

@LoginManager.login_prohibited

The decorator which decorates Responder.route callback.

If user want to access decorated route but he/her must log out, this will call LoginManager._authorized_callback if it's provided.

That can be set by decorating callback with LoginManager.authorized_handler which takes Request and Response. If LoginManager._authorized_callback isn't provided, this will redirect to LoginManager.config["LOGIN_PROHIBITED_ROUTE"] and HTTP Status Code will be set to 307 if it's set.

If not, set LoginManager.config["LOGIN_PROHIBITED_MESSAGE"] to resp.text.

@LoginManager.authorized_handler

This decorates callback to set it LoginManager._authorized_callback

LoginManager.login_user(user)

user must be an instance of user object. Set cookies with Response.set_cookie()

about account data, each value is below:

  • key : LoginManager.config[COOKIE_NAME]["ACCOUNT"]
  • value : user.get_id()
  • expires : if LoginManager.config["COOKIE_REMEMBER_ME"]["ACCOUNT"] (Defaults to True), True, datetime.datetime.now() + LoginManager.config["COOKIE_DURATION"]. Otherwise, None
  • max_age : if LoginManager.config["COOKIE_REMEMBER_ME"]["ACCOUNT"] (Defaults to True), True, LoginManager.config["COOKIE_DURATION"].total_seconds(). Otherwise, None
  • secure : LoginManager.config["COOKIE_SECURE"] Defaults to False _ httponly : LoginManager.config["COOKIE_HTTPONLY"] Defaults to False

about is_fresh:

  • key : LoginManager.config[COOKIE_NAME]["IS_FRESH"]
  • value : 1
  • expires : if LoginManager.config["COOKIE_REMEMBER_ME"]["IS_FRESH"] (Defaults to False), True, datetime.datetime.now() + LoginManager.config["COOKIE_DURATION"]. Otherwise, None
  • max_age : if LoginManager.config["COOKIE_REMEMBER_ME"]["IS_FRESH"] (Defaults to True), True, LoginManager.config["COOKIE_DURATION"].total_seconds(). Otherwise, none
  • secure : LoginManager.config["COOKIE_SECURE"] Defaults to False _ httponly : LoginManager.config["COOKIE_HTTPONLY"] Defaults to False

LoginManager.logout_user()

This log users out by setting cookie that expires and max_age are 0

LoginManager.current_user

This returns instance of user object by searching instance by LoginManager._user_callback. If no user found (callback returned None), this will return LoginManager.anonumous_user

LoginManager.is_fresh

Return If the user logging in is logged in current session(not using Remember me). If logged in current session, returns True. If not, False. This returns False if user is not logged in.

LoginManager.config

  • COOKIE_NAME : The dictionary of cookie name. keys are"ACCOUNT" and "IS_FRESH". Defaults to {"ACCOUNT": "account", "IS_FRESH": "fresh" }
  • COOKIE_REMEMBER_ME : The dictionary of setting whether each cookie is Remember-me. keys are"ACCOUNT" and "IS_FRESH". Defaults to {"ACCOUNT": True, "IS_FRESH": False }
  • COOKIE_DURATION : The amount of time before the cookie expires, as a datetime.timedelta object. Defaults to datetime.timedelta(60)
  • COOKIE_SECURE : Restricts the "Remember Me" cookie's scope to https. Defaults to False
  • COOKIE_HTTPONLY : Prevents the "Remember Me" cookie from being accessed by client-side scripts. Defaults to False
  • LOGIN_REQUIRED_ROUTE : The route redirect to if user is not logged in and tried to access endpoint decorated with LoginManager.login_required. Defaults to None
  • LOGIN_REQUIRED_MESSAGE : The default message to display when users need to log in. Defaults to "Please log in to access this page."
  • LOGIN_PROHIBITED_ROUTE : The route redirect to if user is logged in and tried to access endpoint decorated with LoginManager.login_prohibited. Defaults to None
  • LOGIN_PROHIBITED_MESSAGE : The default message to display when users need to log out. Defaults to "Please log out to access this page."

LoginManager(req=None, resp=None)

req and resp must be an instance of responder.Request and responder.Response or None. This has been added in order to solve problem that req or resp cannot be searched properly in @api.background.task and other specific situation.

This returns deepcopy of self it's LoginManager.set_req_resp is already called.

This is useful in the situation like below

 
def some_func(req, resp):
    @api.background.task
    def inner_func():
        heavy_func(lm(req, resp).current_user)
    resp.content = "yay"

LoginManager.set_req_resp(req=None, resp=None)

req and resp must be an instance of responder.Request and responder.Response or None. This has been added to make LoginManager() callable.

LoginManager() find req and resp by searching it in stack recursively in default. But if you call this and set req and resp, LoginManager will use it forever.

This is nealy useless in most cases, but it's necessary to let LoginManager() become callable.

UserMixin

The simple mixin to make user object.

class UserMixin:

    @property
    def is_active(self):
        return True

    @property
    def is_authenticated(self):
        return True

    @property
    def is_anonymous(self):
        return False

    def get_id(self):
        try:
            return self.id
        except AttributeError:
            raise NotImplementedError('No `id` attribute. override `get_id` or set `id` attribute')

    def __eq__(self, other):
        if isinstance(other, UserMixin):
            return self.get_id() == other.get_id()
        return NotImplemented

    def __ne__(self, other):
        equal = self.__eq__(other)
        if equal is NotImplemented:
            return NotImplemented
        return not equal

AnonymousUserMixin

The user mixin for not logged in users

class AnonymousUserMixin(UserMixin):
    @property
    def is_authenticated(self):
        return False

    @property
    def is_active(self):
        return False

    @property
    def is_anonymous(self):
        return True

    def get_id(self):
        return None

@method_decorator(decorator)

This was made to set decorator to Class-Based-View class's method.

You can use this just like below

@api.route("/")
class Index:
    @method_decorator(lm.login_required)
    def on_get(self, req, resp):
        resp.text = "login-required area."

@class_decorator(decorator, method=("on_get", "on_post", "on_delete", "on_put", "on_head", "on_request"), extra=())

This was made to set decorator to Class-Based-View class.

All method it's name is in method or extra will be decorated with decorator.

You can use this just like below

@api.route("/")
@class_decorator(lm.login_required)
class Index:
    def on_get(self, req, resp):
        resp.text = "login-required area."

About

The simple login/logout management for Responder by kennethreitz

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages