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
The basic idea is based on Flask-Login
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
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()
Set Responder.API
with given api
. This will set current_user
to jinja2
environment variable.
This decorates callback to set it LoginManager._user_callback
.
callback must take one argument and return instance of User object or None
.
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
.
This decorates callback to set it LoginManager._unauthorized_callback
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
.
This decorates callback to set it LoginManager._authorized_callback
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 toTrue
), True,datetime.datetime.now()
+LoginManager.config["COOKIE_DURATION"]
. Otherwise,None
- max_age : if
LoginManager.config["COOKIE_REMEMBER_ME"]["ACCOUNT"]
(Defaults toTrue
), True,LoginManager.config["COOKIE_DURATION"].total_seconds()
. Otherwise,None
- secure :
LoginManager.config["COOKIE_SECURE"]
Defaults toFalse
_ httponly :LoginManager.config["COOKIE_HTTPONLY"]
Defaults toFalse
about is_fresh
:
- key :
LoginManager.config[COOKIE_NAME]["IS_FRESH"]
- value :
1
- expires : if
LoginManager.config["COOKIE_REMEMBER_ME"]["IS_FRESH"]
(Defaults toFalse
),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 toFalse
_ httponly :LoginManager.config["COOKIE_HTTPONLY"]
Defaults toFalse
This log users out by setting cookie that expires
and max_age
are 0
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
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.
- 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 todatetime.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 toNone
- 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 toNone
- LOGIN_PROHIBITED_MESSAGE : The default message to display when users need to log out. Defaults to
"Please log out to access this page."
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"
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.
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
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
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."